Compare commits

..

2 Commits

Author SHA1 Message Date
holger krekel
3a602ee313 fix ffi 2019-09-09 18:43:03 +02:00
Alexander Krotov
e0c1b91a7a Make dc_msg_get_filemime safe 2019-09-09 18:34:25 +03:00
78 changed files with 6211 additions and 6236 deletions

View File

@@ -12,7 +12,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -23,9 +23,20 @@ commands:
steps:
- *restore-workspace
- *restore-cache
- setup_remote_docker:
docker_layer_caching: true
# TODO: move into image
- run:
name: Install Docker client
command: |
set -x
VER="18.09.2"
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
- run:
name: Test (<< parameters.target >>)
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
command: TARGET=<< parameters.target >> ci/run.sh
no_output_timeout: 15m
jobs:
@@ -41,7 +52,7 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
@@ -50,14 +61,13 @@ jobs:
- run: cargo fetch
- run: rustc +stable --version
- run: rustc +$(cat rust-toolchain) --version
# make sure this git repo doesn't grow too big
- run: git gc
- run: rm -rf .git
- persist_to_workspace:
root: /mnt
paths:
- crate
- save_cache:
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -92,7 +102,7 @@ jobs:
- run: cargo fetch
- run:
name: Test
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
command: TARGET=x86_64-apple-darwin ci/run.sh
test_x86_64-unknown-linux-gnu:
executor: default
@@ -114,18 +124,18 @@ jobs:
build_test_docs_wheel:
docker:
- image: deltachat/coredeps
environment:
TESTS: 1
DOCS: 1
working_directory: /mnt/crate
machine: True
steps:
- *restore-workspace
- *restore-cache
- checkout
# - run: docker pull deltachat/doxygen
- run: docker pull deltachat/coredeps
- run:
name: build docs, run tests and build wheels
command: ci_scripts/run-python.sh
command: ci_scripts/ci_run.sh
environment:
TESTS: 1
DOCS: 1
- run:
name: copying docs and wheels to workspace
command: |
@@ -133,6 +143,7 @@ jobs:
# 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:
@@ -165,16 +176,15 @@ workflows:
test:
jobs:
- cargo_fetch
- build_test_docs_wheel:
requires:
- cargo_fetch
- build_test_docs_wheel
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- cargo_fetch
- rustfmt:
requires:
- cargo_fetch
- clippy:
requires:
- cargo_fetch

4
.gitattributes vendored
View File

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

532
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,10 @@ authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
[build-dependencies]
cc = "1.0.35"
pkg-config = "0.3"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
@@ -47,7 +51,6 @@ quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
[dev-dependencies]
tempfile = "3.0"

38
build.rs Normal file
View File

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

View File

@@ -39,4 +39,8 @@ fi
# Run all the test configurations:
$CARGO_CMD $CARGO_SUBCMD $OPT
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
# Build the ffi lib
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE

22
ci_scripts/ci_run.sh Executable file
View File

@@ -0,0 +1,22 @@
# perform CI jobs on PRs and after merges to master.
# triggered from .circleci/config.yml
set -e -x
export BRANCH=${CIRCLE_BRANCH:-test7}
# run doxygen on c-source (needed by later doc-generation steps).
# XXX modifies the host filesystem docs/xml and docs/html directories
# XXX which you can then only remove with sudo as they belong to root
# XXX we don't do doxygen doc generation with Rust anymore, needs to be
# substituted with rust-docs
#if [ -n "$DOCS" ] ; then
# docker run --rm -it -v $PWD:/mnt -w /mnt/docs deltachat/doxygen doxygen
#fi
# run everything else inside docker (TESTS, DOCS, WHEELS)
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
--rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh

View File

@@ -36,21 +36,19 @@ if [ -n "$TESTS" ]; then
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# run tox
# XXX we don't run liveconfig tests because they hang sometimes
# see https://github.com/deltachat/deltachat-core-rust/issues/331
# unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e py37
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels
popd
fi
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs
# echo -----------------------
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
# fi
if [ -n "$DOCS" ]; then
echo -----------------------
echo generating python docs
echo -----------------------
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
fi

View File

@@ -16,7 +16,6 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
deltachat = { path = "../", default-features = false }
deltachat-provider-overview = { git = "https://github.com/deltachat/provider-overview", rev = "366b41a7503973e4ffac3aa5173b419f2f03c211" }
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"

View File

@@ -393,9 +393,8 @@ int dc_set_config (dc_context_t* context, const char*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
* @param key The key to query.
* @return Returns current value of "key", if "key" is unset, the default
* value is returned. The returned value must be free()'d, NULL is never
* returned. If there is an error an empty string will be returned.
* @return Returns current value of "key", if "key" is unset, the default value is returned.
* The returned value must be free()'d, NULL is never returned.
*/
char* dc_get_config (dc_context_t* context, const char* key);
@@ -442,112 +441,7 @@ char* dc_get_info (dc_context_t* context);
*/
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
/**
* Opaque object containing information about one single email provider.
*/
typedef struct _dc_provider dc_provider_t;
/**
* Create a provider struct for the given domain.
*
* @param doamin The domain to get provider info for.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_domain (char* domain);
/**
* Create a provider struct for the given email address.
*
* The provider is extracted from the email address and it's information is returned.
*
* @param email The user's email address to extract the provider info form.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_email (char* email);
/**
* URL of the overview page.
*
* This URL allows linking to the providers page on providers.delta.chat.
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_overview_page (const dc_provider_t* provider);
/**
* The provider's name.
*
* The name of the provider, e.g. "POSTEO".
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_name (const dc_provider_t* provider);
/**
* The markdown content of the providers page.
*
* This contains the preparation steps or additional information if the status
* is DC_PROVIDER_STATUS_BROKEN.
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_markdown (const dc_provider_t* provider);
/**
* Date of when the state was last checked/updated.
*
* This is returned as a string.
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_status_date (const dc_provider_t* provider);
/**
* Whether DC works with this provider.
*
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
*
* @param provider The dc_provider_t struct.
* @return The status as a constant number.
*/
int dc_provider_get_status (const dc_provider_t* provider);
/**
* Free the provider info struct.
*
* @param provider The dc_provider_t struct.
*/
void dc_provider_unref (const dc_provider_t* provider);
/**
* Provider status returned by dc_provider_get_status().
*
* Works right out of the box without any preperation steps needed
*/
#define DC_PROVIDER_STATUS_OK 1
/**
* Provider status returned by dc_provider_get_status().
*
* Works, but preparation steps are needed
*/
#define DC_PROVIDER_STATUS_PREPARATION 2
/**
* Provider status returned by dc_provider_get_status().
*
* Doesn't work (too unstable to use falls also in this category)
*/
#define DC_PROVIDER_STATUS_BROKEN 3
// connect

File diff suppressed because it is too large Load Diff

View File

@@ -1,92 +0,0 @@
extern crate deltachat_provider_overview;
use std::ptr;
use deltachat::dc_tools::{as_str, StrExt};
use deltachat_provider_overview::StatusState;
#[no_mangle]
pub type dc_provider_t = deltachat_provider_overview::Provider;
#[no_mangle]
pub unsafe extern "C" fn dc_provider_new_from_domain(
domain: *const libc::c_char,
) -> *const dc_provider_t {
match deltachat_provider_overview::get_provider_info(as_str(domain)) {
Some(provider) => provider,
None => ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_new_from_email(
email: *const libc::c_char,
) -> *const dc_provider_t {
let domain = deltachat_provider_overview::get_domain_from_email(as_str(email));
match deltachat_provider_overview::get_provider_info(domain) {
Some(provider) => provider,
None => ptr::null(),
}
}
macro_rules! null_guard {
($context:tt) => {
if $context.is_null() {
return ptr::null_mut() as *mut libc::c_char;
}
};
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_overview_page(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
format!(
"{}/{}",
deltachat_provider_overview::PROVIDER_OVERVIEW_URL,
(*provider).overview_page
)
.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
null_guard!(provider);
(*provider).name.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_markdown(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
(*provider).markdown.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_status_date(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
(*provider).status.date.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
if provider.is_null() {
return 0;
}
match (*provider).status.state {
StatusState::OK => 1,
StatusState::PREPARATION => 2,
StatusState::BROKEN => 3,
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
()
}
// TODO expose general provider overview url?

View File

@@ -20,17 +20,18 @@ use deltachat::message::*;
use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::sql;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::Event;
use num_traits::FromPrimitive;
/// Reset database tables. This function is called from Core cmdline.
/// Argument is a bitmask, executing single or multiple actions in one call.
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
info!(context, "Resetting tables ({})...", bits);
info!(context, 0, "Resetting tables ({})...", bits);
if 0 != bits & 1 {
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
info!(context, "(1) Jobs reset.");
info!(context, 0, "(1) Jobs reset.");
}
if 0 != bits & 2 {
sql::execute(
@@ -40,11 +41,11 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
params![],
)
.unwrap();
info!(context, "(2) Peerstates reset.");
info!(context, 0, "(2) Peerstates reset.");
}
if 0 != bits & 4 {
sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap();
info!(context, "(4) Private keypairs reset.");
info!(context, 0, "(4) Private keypairs reset.");
}
if 0 != bits & 8 {
sql::execute(
@@ -83,13 +84,10 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
)
.unwrap();
sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap();
info!(context, "(8) Rest but server config reset.");
info!(context, 0, "(8) Rest but server config reset.");
}
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0, 0);
1
}
@@ -98,7 +96,7 @@ unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) ->
/* mainly for testing, may be called by dc_import_spec() */
let mut success: libc::c_int = 0i32;
let mut data: *mut libc::c_char = ptr::null_mut();
let mut data_bytes = 0;
let mut data_bytes: size_t = 0;
if !(dc_read_file(
context,
filename,
@@ -124,7 +122,7 @@ unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) ->
/// @return 1=success, 0=error.
unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int {
if !context.sql.is_open() {
error!(context, "Import: Database not opened.");
error!(context, 0, "Import: Database not opened.");
return 0;
}
@@ -145,7 +143,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
} else {
let rs = context.sql.get_config(context, "import_spec");
if rs.is_none() {
error!(context, "Import: No file or folder given.");
error!(context, 0, "Import: No file or folder given.");
ok_to_continue = false;
} else {
ok_to_continue = true;
@@ -168,6 +166,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
if dir.is_err() {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(real_spec),
);
@@ -183,7 +182,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
let name = name_f.to_string_lossy();
if name.ends_with(".eml") {
let path_plus_name = format!("{}/{}", as_str(real_spec), name);
info!(context, "Import: {}", path_plus_name);
info!(context, 0, "Import: {}", path_plus_name);
let path_plus_name_c = CString::yolo(path_plus_name);
if 0 != dc_poke_eml_file(context, path_plus_name_c.as_ptr()) {
read_cnt += 1
@@ -196,15 +195,13 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
if ok_to_continue2 {
info!(
context,
0,
"Import: {} items read from \"{}\".",
read_cnt,
as_str(real_spec)
);
if read_cnt > 0 {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
}
success = 1
}
@@ -231,10 +228,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let msgtext = dc_msg_get_text(msg);
info!(
context,
0,
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
prefix.as_ref(),
dc_msg_get_id(msg) as libc::c_int,
if dc_msg_get_showpadlock(msg) {
if 0 != dc_msg_get_showpadlock(msg) {
"🔒"
} else {
""
@@ -253,7 +251,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
} else {
"[FRESH]"
},
if dc_msg_is_info(msg) { "[INFO]" } else { "" },
if 0 != dc_msg_is_info(msg) {
"[INFO]"
} else {
""
},
statestr,
&temp2,
);
@@ -266,6 +268,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
if msg_id == 9 as libc::c_uint {
info!(
context,
0,
"--------------------------------------------------------------------------------"
);
@@ -273,7 +276,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
} else if msg_id > 0 {
if lines_out == 0 {
info!(
context,
context, 0,
"--------------------------------------------------------------------------------",
);
lines_out += 1
@@ -285,7 +288,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<u32>) -> Result<(), Error
if lines_out > 0 {
info!(
context,
"--------------------------------------------------------------------------------"
0, "--------------------------------------------------------------------------------"
);
}
Ok(())
@@ -302,7 +305,7 @@ unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
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(context);
let verified_state = contact.is_verified();
let verified_str = if VerifiedStatus::Unverified != verified_state {
if verified_state == VerifiedStatus::BidirectVerified {
" √√"
@@ -334,7 +337,7 @@ unsafe fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
);
}
info!(context, "Contact#{}: {}{}", contact_id, line, line2);
info!(context, 0, "Contact#{}: {}{}", contact_id, line, line2);
}
}
}
@@ -452,6 +455,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
============================================="
),
},
"open" => {
ensure!(!arg1.is_empty(), "Argument <file> missing");
dc_close(context);
ensure!(dc_open(context, arg1, None), "Open failed");
}
"close" => {
dc_close(context);
}
"initiate-key-transfer" => {
let setup_code = dc_initiate_key_transfer(context);
if !setup_code.is_null() {
@@ -469,7 +480,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let msg_id: u32 = arg1.parse()?;
let msg = dc_get_msg(context, msg_id)?;
if dc_msg_is_setupmessage(&msg) {
let setupcodebegin = dc_msg_get_setupcodebegin(context, &msg);
let setupcodebegin = dc_msg_get_setupcodebegin(&msg);
println!(
"The setup code for setup message Msg#{} starts with: {}",
msg_id,
@@ -485,7 +496,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 !dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
if 0 == dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
bail!("Continue key transfer failed");
}
}
@@ -496,28 +507,32 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
}
"export-backup" => {
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
dc_imex(context, 11, context.get_blobdir(), ptr::null());
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, Some(arg1), ptr::null());
dc_imex(context, 12, arg1_c, ptr::null());
}
"export-keys" => {
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
dc_imex(context, 1, context.get_blobdir(), ptr::null());
}
"import-keys" => {
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
dc_imex(context, 2, context.get_blobdir(), ptr::null());
}
"export-setup" => {
let setup_code = dc_create_setup_code(context);
let file_name = context.get_blobdir().join("autocrypt-setup-message.html");
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(&file_name, file_content)?;
std::fs::write(as_str(file_name), file_content)?;
println!(
"Setup message written to: {}\nSetup code: {}",
file_name.display(),
as_str(file_name),
&setup_code,
);
free(file_name as *mut libc::c_void);
}
"poke" => {
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
@@ -544,7 +559,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{}={:?}", key, val);
}
"info" => {
println!("{:#?}", context.get_info());
println!("{}", to_string(dc_get_info(context)));
}
"maybenetwork" => {
maybe_network(context);
@@ -564,16 +579,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let cnt = chatlist.len();
if cnt > 0 {
info!(
context,
context, 0,
"================================================================================"
);
for i in (0..cnt).rev() {
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
let temp_subtitle = chat.get_subtitle(context);
let temp_subtitle = chat.get_subtitle();
let temp_name = chat.get_name();
info!(
context,
0,
"{}#{}: {} [{}] [{} fresh]",
chat_prefix(&chat),
chat.get_id(),
@@ -581,7 +597,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
temp_subtitle,
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
let lot = chatlist.get_summary(i, Some(&chat));
let statestr = if chat.is_archived() {
" [Archived]"
} else {
@@ -598,6 +614,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let text2 = lot.get_text2();
info!(
context,
0,
"{}{}{}{} [{}]{}",
text1.unwrap_or(""),
if text1.is_some() { ": " } else { "" },
@@ -611,13 +628,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
info!(
context,
context, 0,
"================================================================================"
);
}
}
if location::is_sending_locations_to_chat(context, 0) {
info!(context, "Location streaming enabled.");
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
info!(context, 0, "Location streaming enabled.");
}
println!("{} chats", cnt);
}
@@ -636,10 +653,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let sel_chat = sel_chat.as_ref().unwrap();
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, 0);
let temp2 = sel_chat.get_subtitle(context);
let temp2 = sel_chat.get_subtitle();
let temp_name = sel_chat.get_name();
info!(
context,
0,
"{}#{}: {} [{}]{}",
chat_prefix(sel_chat),
sel_chat.get_id(),
@@ -652,7 +670,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
log_msglist(context, &msglist)?;
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
if let Ok(draft) = chat::get_draft(context, sel_chat.get_id()) {
log_msg(context, "Draft", &draft);
}
@@ -665,7 +683,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"createchat" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id: libc::c_int = arg1.parse()?;
let chat_id = chat::create_by_contact_id(context, contact_id as u32)?;
let chat_id = chat::create_by_contact_id(context, contact_id as uint32_t)?;
println!("Single#{} created successfully.", chat_id,);
}
@@ -694,10 +712,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id_0: libc::c_int = arg1.parse()?;
if chat::add_contact_to_chat(
if 0 != chat::add_contact_to_chat(
context,
sel_chat.as_ref().unwrap().get_id(),
contact_id_0 as u32,
contact_id_0 as uint32_t,
) {
println!("Contact added to chat.");
} else {
@@ -711,7 +729,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
chat::remove_contact_from_chat(
context,
sel_chat.as_ref().unwrap().get_id(),
contact_id_1 as u32,
contact_id_1 as uint32_t,
)?;
println!("Contact added to chat.");
@@ -735,7 +753,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(sel_chat.is_some(), "No chat selected.");
let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id());
info!(context, "Memberlist:");
info!(context, 0, "Memberlist:");
log_contactlist(context, &contacts);
println!(
@@ -763,6 +781,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let marker = location.marker.as_ref().unwrap_or(&default_marker);
info!(
context,
0,
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
location.location_id,
dc_timestamp_to_str(location.timestamp),
@@ -776,7 +795,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
}
if locations.is_empty() {
info!(context, "No locations.");
info!(context, 0, "No locations.");
}
}
"sendlocations" => {
@@ -825,11 +844,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(sel_chat.is_some(), "No chat selected.");
ensure!(!arg1.is_empty(), "No file given.");
let mut msg = dc_msg_new(if arg0 == "sendimage" {
Viewtype::Image
} else {
Viewtype::File
});
let mut msg = dc_msg_new(
context,
if arg0 == "sendimage" {
Viewtype::Image
} else {
Viewtype::File
},
);
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
if !arg2.is_empty() {
dc_msg_set_text(&mut msg, arg2_c);
@@ -845,7 +867,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
0 as libc::c_uint
};
let msglist = context.search_msgs(chat, arg1);
let msglist = dc_search_msgs(context, chat, arg1_c);
log_msglist(context, &msglist)?;
println!("{} messages.", msglist.len());
@@ -854,7 +876,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(sel_chat.is_some(), "No chat selected.");
if !arg1.is_empty() {
let mut draft = dc_msg_new(Viewtype::Text);
let mut draft = dc_msg_new(context, Viewtype::Text);
dc_msg_set_text(&mut draft, arg1_c);
chat::set_draft(
context,
@@ -908,7 +930,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{}", as_str(res));
}
"listfresh" => {
let msglist = context.get_fresh_msgs();
let msglist = dc_get_fresh_msgs(context);
log_msglist(context, &msglist)?;
print!("{} fresh messages.", msglist.len());
@@ -1014,17 +1036,16 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
res.get_text2()
);
}
// TODO: implement this again, unclear how to match this through though, without writing a parser.
// "event" => {
// ensure!(!arg1.is_empty(), "Argument <id> missing.");
// let event = arg1.parse()?;
// let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?;
// let r = context.call_cb(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t);
// println!(
// "Sending event {:?}({}), received value {}.",
// event, event as usize, r as libc::c_int,
// );
// }
"event" => {
ensure!(!arg1.is_empty(), "Argument <id> missing.");
let event = arg1.parse()?;
let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?;
let r = context.call_cb(event, 0 as uintptr_t, 0 as uintptr_t);
println!(
"Sending event {:?}({}), received value {}.",
event, event as usize, r as libc::c_int,
);
}
"fileinfo" => {
ensure!(!arg1.is_empty(), "Argument <file> missing.");

View File

@@ -15,20 +15,21 @@ extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::context::*;
use deltachat::dc_tools::*;
use deltachat::job::*;
use deltachat::oauth2::*;
use deltachat::securejoin::*;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::Event;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
@@ -43,75 +44,96 @@ use self::cmdline::*;
// Event Handler
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
unsafe extern "C" fn receive_event(
_context: &Context,
event: Event,
data1: uintptr_t,
data2: uintptr_t,
) -> uintptr_t {
match event {
Event::GetString { .. } => {}
Event::Info(msg) => {
Event::GET_STRING => {}
Event::INFO => {
/* do not show the event as this would fill the screen */
println!("{}", msg);
println!("{}", to_string(data2 as *const _),);
}
Event::SmtpConnected(msg) => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
Event::SMTP_CONNECTED => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
}
Event::ImapConnected(msg) => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
Event::IMAP_CONNECTED => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
}
Event::SmtpMessageSent(msg) => {
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
}
Event::Warning(msg) => {
println!("[Warning] {}", msg);
}
Event::Error(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
}
Event::ErrorNetwork(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
}
Event::ErrorSelfNotInGroup(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
}
Event::MsgsChanged { chat_id, msg_id } => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
chat_id, msg_id,
Event::SMTP_MESSAGE_SENT => {
println!(
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
to_string(data2 as *const _),
);
}
Event::ContactsChanged(_) => {
Event::WARNING => {
println!("[Warning] {}", to_string(data2 as *const _),);
}
Event::ERROR => {
println!(
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
to_string(data2 as *const _),
);
}
Event::ERROR_NETWORK => {
println!(
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
data1 as usize,
to_string(data2 as *const _),
);
}
Event::ERROR_SELF_NOT_IN_GROUP => {
println!(
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
to_string(data2 as *const _),
);
}
Event::MSGS_CHANGED => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
data1 as usize, data2 as usize,
);
}
Event::CONTACTS_CHANGED => {
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
}
Event::LocationChanged(contact) => {
Event::LOCATION_CHANGED => {
print!(
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
contact,
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
data1 as usize,
);
}
Event::ConfigureProgress(progress) => {
Event::CONFIGURE_PROGRESS => {
print!(
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
progress,
data1 as usize,
);
}
Event::ImexProgress(progress) => {
Event::IMEX_PROGRESS => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
progress,
data1 as usize,
);
}
Event::ImexFileWritten(file) => {
Event::IMEX_FILE_WRITTEN => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
file.display()
to_string(data1 as *const _)
);
}
Event::ChatModified(chat) => {
Event::CHAT_MODIFIED => {
print!(
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
chat
data1 as usize,
);
}
_ => {
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
print!(
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
event, data1 as usize, data2 as usize,
);
}
}
@@ -365,15 +387,15 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
if args.len() < 2 {
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
if args.len() == 2 {
if unsafe { !dc_open(&mut context, &args[1], None) } {
println!("Error: Cannot open {}.", args[0],);
}
} else if args.len() != 1 {
println!("Error: Bad arguments, expected [db-name].");
return Err(format_err!("No db-name specified"));
}
let context = Context::new(
Box::new(receive_event),
"CLI".into(),
Path::new(&args[1]).to_path_buf(),
)?;
println!("Delta Chat Core is awaiting your commands.");

View File

@@ -1,5 +1,6 @@
extern crate deltachat;
use std::ffi::CStr;
use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
@@ -8,43 +9,42 @@ use deltachat::chat;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::configure::*;
use deltachat::constants::Event;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::job::{
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
};
use deltachat::Event;
fn cb(_ctx: &Context, event: Event) -> usize {
print!("[{:?}]", event);
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
match event {
Event::ConfigureProgress(progress) => {
print!(" progress: {}\n", progress);
Event::CONFIGURE_PROGRESS => {
println!(" progress: {}", data1);
0
}
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
print!(" {}\n", msg);
0
}
_ => {
print!("\n");
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
println!(
" {}",
unsafe { CStr::from_ptr(data2 as *const _) }
.to_str()
.unwrap()
);
0
}
_ => 0,
}
}
fn main() {
unsafe {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("creating database {:?}", dbfile);
let ctx =
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let running = Arc::new(RwLock::new(true));
let info = ctx.get_info();
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
let duration = time::Duration::from_millis(4000);
println!("info: {:#?}", info);
println!("info: {}", info_s.to_str().unwrap());
let ctx = Arc::new(ctx);
let ctx1 = ctx.clone();
@@ -73,6 +73,13 @@ fn main() {
}
});
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("opening database {:?}", dbfile);
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
println!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");
@@ -94,7 +101,7 @@ fn main() {
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
for i in 0..chats.len() {
let summary = chats.get_summary(&ctx, 0, None);
let summary = chats.get_summary(0, None);
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
@@ -123,5 +130,6 @@ fn main() {
t2.join().unwrap();
println!("closing");
dc_close(&ctx);
}
}

52
misc.c Normal file
View File

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

1
misc.h Normal file
View File

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

View File

@@ -65,7 +65,7 @@ Afterwards ``which python`` tells you that it comes out of the "venv"
directory that contains all python install artifacts. Let's first
install test tools::
pip install pytest pytest-timeout pytest-rerunfailures requests
pip install pytest pytest-timeout requests
then cargo-build and install the deltachat bindings::

View File

@@ -50,8 +50,9 @@ class Account(object):
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
def __del__(self):
self.shutdown()
# XXX this can cause "illegal instructions" at test ends so we omit it for now
# def __del__(self):
# self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -396,8 +397,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"):
# print("SHUTDOWN", self)
self.stop_threads(wait=False)
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
deltachat.clear_context_callback(self._dc_context)
@@ -483,10 +483,6 @@ class EventLogger:
def set_timeout(self, timeout):
self._timeout = timeout
def consume_events(self, check_error=True):
while not self._event_queue.empty():
self.get()
def get(self, timeout=None, check_error=True):
timeout = timeout or self._timeout
ev = self._event_queue.get(timeout=timeout)

View File

@@ -1,7 +1,6 @@
""" 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
@@ -305,6 +304,7 @@ class Chat(object):
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:raises ValueError: if contact could not be added
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
"""
@@ -315,46 +315,3 @@ class Chat(object):
return list(iter_array(
dc_array, lambda id: Contact(self._dc_context, id))
)
def set_profile_image(self, img_path):
"""Set group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:params img_path: path to image object
:raises ValueError: if profile image could not be set
:returns: None
"""
assert os.path.exists(img_path), img_path
p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p))
def remove_profile_image(self):
"""remove group profile image.
If the group is already promoted (any message was sent to the group),
all group members are informed by a special status message that is sent
automatically by this function.
:raises ValueError: if profile image could not be reset
:returns: None
"""
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
if res != 1:
raise ValueError("Removing Profile Image failed")
def get_profile_image(self):
"""Get group profile image.
For groups, this is the image set by any group member using
set_chat_profile_image(). For normal chats, this is the image
set by each remote user on their own using dc_set_config(context,
"selfavatar", image).
:returns: path to profile image, None if no profile image exists.
"""
dc_res = lib.dc_chat_get_profile_image(self._dc_chat)
if dc_res == ffi.NULL:
return None
return from_dc_charpointer(dc_res)

View File

@@ -8,9 +8,6 @@ from os.path import join as joinpath
# this works well when you in a git-checkout
# run "python deltachat/const.py" to regenerate events
# begin const generated
DC_PROVIDER_STATUS_OK = 1
DC_PROVIDER_STATUS_PREPARATION = 2
DC_PROVIDER_STATUS_BROKEN = 3
DC_GCL_ARCHIVED_ONLY = 0x01
DC_GCL_NO_SPECIALS = 0x02
DC_GCL_ADD_ALLDONE_HINT = 0x04
@@ -87,8 +84,7 @@ DC_EVENT_IS_OFFLINE = 2081
def read_event_defines(f):
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|'
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
for line in f:
m = rex.match(line)
if m:

View File

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

View File

@@ -1,67 +0,0 @@
"""Provider info class."""
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer
class ProviderNotFoundError(Exception):
"""The provider information was not found."""
class Provider(object):
"""Provider information.
:param domain: The domain to get the provider info for, this is
normally the part following the `@` of the domain.
"""
def __init__(self, domain):
provider = ffi.gc(
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
lib.dc_provider_unref,
)
if provider == ffi.NULL:
raise ProviderNotFoundError("Provider not found")
self._provider = provider
@classmethod
def from_email(cls, email):
"""Create provider info from an email address.
:param email: Email address to get provider info for.
"""
return cls(email.split('@')[-1])
@property
def overview_page(self):
"""URL to the overview page of the provider on providers.delta.chat."""
return from_dc_charpointer(
lib.dc_provider_get_overview_page(self._provider))
@property
def name(self):
"""The name of the provider."""
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
@property
def markdown(self):
"""Content of the information page, formatted as markdown."""
return from_dc_charpointer(
lib.dc_provider_get_markdown(self._provider))
@property
def status_date(self):
"""The date the provider info was last updated, as a string."""
return from_dc_charpointer(
lib.dc_provider_get_status_date(self._provider))
@property
def status(self):
"""The status of the provider information.
This is one of the
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
"""
return lib.dc_provider_get_status(self._provider)

View File

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

View File

@@ -147,15 +147,6 @@ class TestOfflineChat:
qr = chat.get_join_qr()
assert ac2.check_qr(qr).is_ask_verifygroup
def test_get_set_profile_image_simple(self, ac1, data):
chat = ac1.create_group_chat(name="title1")
p = data.get_path("d.png")
chat.set_profile_image(p)
p2 = chat.get_profile_image()
assert open(p, "rb").read() == open(p2, "rb").read()
chat.remove_profile_image()
assert chat.get_profile_image() is None
def test_delete_and_send_fails(self, ac1, chat1):
chat1.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -334,11 +325,10 @@ class TestOfflineChat:
class TestOnlineAccount:
def get_chat(self, ac1, ac2):
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
return chat
def test_one_account_init(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
def test_one_account_send(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
@@ -354,8 +344,15 @@ class TestOnlineAccount:
assert ev[1] == msg_out.id
def test_two_accounts_send_receive(self, acfactory):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
msg_out = chat.send_text("message1")
@@ -366,8 +363,15 @@ class TestOnlineAccount:
assert msg_in.text == "message1"
def test_forward_messages(self, acfactory):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
msg_out = chat.send_text("message2")
@@ -391,10 +395,15 @@ class TestOnlineAccount:
assert not chat3.get_messages()
def test_send_and_receive_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
@@ -436,10 +445,15 @@ class TestOnlineAccount:
assert msg_out.is_out_mdn_received()
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
@@ -462,12 +476,14 @@ class TestOnlineAccount:
assert msg_back.text == "message-back"
def test_saved_mime_on_received_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("configure ac2 to save mime headers, create ac1/ac2 chat")
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
ac2.set_config("save_mime_headers", "1")
chat = self.get_chat(ac1, ac2)
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
@@ -481,8 +497,14 @@ class TestOnlineAccount:
assert mime.get_all("Received")
def test_send_and_receive_image(self, acfactory, lp, data):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("sending image message from ac1 to ac2")
path = data.get_path("d.png")
@@ -502,13 +524,13 @@ class TestOnlineAccount:
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online(self, acfactory, tmpdir):
backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
path = ac1.export_to_dir(backupdir.strpath)
assert os.path.exists(path)
@@ -537,16 +559,16 @@ class TestOnlineAccount:
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
# first try a bad setup code
with pytest.raises(ValueError):
msg.continue_key_transfer(str(reversed(setup_code)))
print("*************** Incoming ASM File at: ", msg.filename)
print("*************** Setup Code: ", setup_code)
msg.continue_key_transfer(setup_code)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_qr_setup_contact(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
qr = ac1.get_setup_contact_qr()
lp.sec("ac2: start QR-code based setup contact protocol")
@@ -555,64 +577,15 @@ class TestOnlineAccount:
wait_securejoin_inviter_progress(ac1, 1000)
def test_qr_join_chat(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
chat = ac1.create_group_chat("hello")
qr = chat.get_join_qr()
lp.sec("ac2: start QR-code based join-group protocol")
ch = ac2.qr_join_chat(qr)
assert ch.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
def test_set_get_profile_image(self, acfactory, data, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("create unpromoted group chat")
chat = ac1.create_group_chat("hello")
p = data.get_path("d.png")
lp.sec("ac1: set profile image on unpromoted chat")
chat.set_profile_image(p)
ac1._evlogger.get_matching("DC_EVENT_CHAT_MODIFIED")
assert not chat.is_promoted()
lp.sec("ac1: send text to promote chat (XXX without contact added)")
# XXX first promote the chat before adding contact
# because DC does not send out profile images for unpromoted chats
# otherwise
chat.send_text("ac1: initial message to promote chat (workaround)")
assert chat.is_promoted()
lp.sec("ac2: add ac1 to a chat so the message does not land in DEADDROP")
c1 = ac2.create_contact(email=ac1.get_config("addr"))
ac2.create_chat_by_contact(c1)
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
lp.sec("ac1: add ac2 to promoted group chat")
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat.add_contact(c2)
lp.sec("ac1: send a first message to ac2")
chat.send_text("hi")
assert chat.is_promoted()
lp.sec("ac2: wait for receiving message from ac1")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
msg_in = ac2.get_message_by_id(ev[2])
assert not msg_in.chat.is_deaddrop()
lp.sec("ac2: create chat and read profile image")
chat2 = ac2.create_chat_by_message(msg_in)
p2 = chat2.get_profile_image()
assert p2 is not None
assert open(p2, "rb").read() == open(p, "rb").read()
ac2._evlogger.consume_events()
ac1._evlogger.consume_events()
lp.sec("ac2: delete profile image from chat")
chat2.remove_profile_image()
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
chat1b = ac1.create_chat_by_message(ev[2])
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None

View File

@@ -4,7 +4,7 @@ from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed
class TestOnlineInCreation:
class TestInCreation:
def test_forward_increation(self, acfactory, data, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()

View File

@@ -1,5 +1,5 @@
from __future__ import print_function
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
from deltachat import capi, const, set_context_callback, clear_context_callback
from deltachat.capi import ffi
from deltachat.capi import lib
from deltachat.account import EventLogger
@@ -17,19 +17,11 @@ def test_callback_None2int():
clear_context_callback(ctx)
def test_dc_close_events(tmpdir):
ctx = ffi.gc(
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
def test_dc_close_events():
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
evlog = EventLogger(ctx)
evlog.set_timeout(5)
set_context_callback(
ctx,
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
)
p = tmpdir.join("hello.db")
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
capi.lib.dc_close(ctx)
def find(info_string):
@@ -83,18 +75,3 @@ def test_markseen_invalid_message_ids(acfactory):
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")
def test_provider_info():
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
assert cutil.from_dc_charpointer(
lib.dc_provider_get_overview_page(provider)
) == "https://providers.delta.chat/example.com"
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL

View File

@@ -1,27 +0,0 @@
import pytest
from deltachat import const
from deltachat import provider
def test_provider_info_from_email():
example = provider.Provider.from_email("email@example.com")
assert example.overview_page == "https://providers.delta.chat/example.com"
assert example.name == "Example"
assert example.markdown == "\n..."
assert example.status_date == "2018-09"
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_from_domain():
example = provider.Provider("example.com")
assert example.overview_page == "https://providers.delta.chat/example.com"
assert example.name == "Example"
assert example.markdown == "\n..."
assert example.status_date == "2018-09"
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():
with pytest.raises(provider.ProviderNotFoundError):
provider.Provider.from_email("email@unexistent.no")

View File

@@ -16,15 +16,12 @@ passenv =
DCC_PY_LIVECONFIG
deps =
pytest
pytest-rerunfailures
pytest-timeout
pytest-xdist
pytest-faulthandler
pdbpp
requests
[testenv:auditwheels]
skipsdist = True
deps = auditwheel
commands =
python tests/auditwheels.py {toxworkdir}/wheelhouse
@@ -45,7 +42,7 @@ commands =
[testenv:doc]
basepython = python3.5
deps =
sphinx==2.2.0
sphinx==2.0.1
breathe
changedir = doc
@@ -54,12 +51,11 @@ commands =
[pytest]
addopts = -v -rs --reruns 3 --reruns-delay 2
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true
timeout = 60
timeout_method = thread
timeout = 60
[flake8]
max-line-length = 120

372
spec.md
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -31,13 +31,17 @@ use crate::stock::StockMessage;
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
/// present and sorted into the list by date. Rendering the deaddrop in the described way
/// would not add extra work in the UI then.
#[derive(Debug)]
pub struct Chatlist {
pub struct Chatlist<'a> {
context: &'a Context,
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, u32)>,
}
impl Chatlist {
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.
///
@@ -81,7 +85,7 @@ impl Chatlist {
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned.
pub fn try_load(
context: &Context,
context: &'a Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
@@ -196,7 +200,7 @@ impl Chatlist {
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
}
Ok(Chatlist { ids })
Ok(Chatlist { context, ids })
}
/// Find out the number of chats.
@@ -243,7 +247,7 @@ impl Chatlist {
/// - 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 fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
pub fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
// 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".
@@ -259,7 +263,7 @@ impl Chatlist {
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
@@ -271,11 +275,11 @@ impl Chatlist {
let mut lastcontact = None;
let lastmsg = if 0 != lastmsg_id {
if let Ok(lastmsg) = dc_msg_load_from_db(context, lastmsg_id) {
if let Ok(lastmsg) = dc_msg_load_from_db(self.context, lastmsg_id) {
if lastmsg.from_id != 1 as libc::c_uint
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
lastcontact = Contact::load_from_db(self.context, lastmsg.from_id).ok();
}
Some(lastmsg)
@@ -290,9 +294,14 @@ impl Chatlist {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
{
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string());
} else {
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
ret.fill(
&mut lastmsg.unwrap(),
chat,
lastcontact.as_ref(),
self.context,
);
}
ret
@@ -302,10 +311,11 @@ impl Chatlist {
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
@@ -315,7 +325,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// only few fresh messages.
context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
@@ -323,6 +333,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}

View File

@@ -73,7 +73,7 @@ impl Context {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),

View File

@@ -1,7 +1,6 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
@@ -70,6 +69,7 @@ pub unsafe fn moz_autoconfigure(
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -87,7 +87,7 @@ pub unsafe fn moz_autoconfigure(
|| moz_ac.out.send_port == 0
{
let r = moz_ac.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(xml_raw as *mut libc::c_void);
return None;
}
@@ -121,13 +121,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
moz_ac.out.server_flags |= 0x200
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
moz_ac.out.server_flags |= 0x100
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
moz_ac.out.server_flags |= 0x400
}
}
_ => {}
@@ -140,13 +140,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
moz_ac.out.server_flags |= 0x20000
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
moz_ac.out.server_flags |= 0x10000
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
moz_ac.out.server_flags |= 0x40000
}
}
_ => {}

View File

@@ -1,7 +1,6 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::login_param::LoginParam;
@@ -76,6 +75,7 @@ pub unsafe fn outlk_autodiscover(
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -109,7 +109,7 @@ pub unsafe fn outlk_autodiscover(
|| outlk_ad.out.send_port == 0
{
let r = outlk_ad.out.to_string();
warn!(context, "Bad or incomplete autoconfig: {}", r,);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
@@ -168,9 +168,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
outlk_ad.out.mail_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
outlk_ad.out.server_flags |= 0x200
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
outlk_ad.out.server_flags |= 0x400
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
@@ -182,9 +182,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
outlk_ad.out.send_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
outlk_ad.out.server_flags |= 0x20000
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
outlk_ad.out.server_flags |= 0x40000
}
outlk_ad.out_smtp_set = 1
}

View File

@@ -1,6 +1,7 @@
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::constants::*;
use crate::constants::Event;
use crate::constants::DC_CREATE_MVBOX;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
@@ -9,6 +10,7 @@ use crate::job::*;
use crate::login_param::LoginParam;
use crate::oauth2::*;
use crate::param::Params;
use crate::types::*;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
@@ -18,17 +20,24 @@ use auto_mozilla::moz_autoconfigure;
macro_rules! progress {
($context:tt, $progress:expr) => {
assert!(
$progress > 0 && $progress <= 1000,
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
$context.call_cb(
Event::CONFIGURE_PROGRESS,
$progress as uintptr_t,
0 as uintptr_t,
);
};
}
// connect
pub unsafe fn configure(context: &Context) {
if dc_has_ongoing(context) {
warn!(context, "There is already another ongoing process running.",);
warn!(
context,
0, "There is already another ongoing process running.",
);
return;
}
job_kill_action(context, Action::ConfigureImap);
@@ -36,8 +45,17 @@ pub unsafe fn configure(context: &Context) {
}
/// Check if the context is already configured.
pub fn dc_is_configured(context: &Context) -> bool {
context.sql.get_config_bool(context, "configured")
pub fn dc_is_configured(context: &Context) -> libc::c_int {
if context
.sql
.get_config_int(context, "configured")
.unwrap_or_default()
> 0
{
1
} else {
0
}
}
/*******************************************************************************
@@ -55,7 +73,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
if dc_alloc_ongoing(context) {
ongoing_allocated_here = true;
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
error!(context, 0, "Cannot configure, database not opened.",);
} else {
context.inbox.read().unwrap().disconnect(context);
context
@@ -71,7 +89,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
.imap
.disconnect(context);
context.smtp.clone().lock().unwrap().disconnect();
info!(context, "Configure ...",);
info!(context, 0, "Configure ...",);
let s_a = context.running_state.clone();
let s = s_a.read().unwrap();
@@ -95,7 +113,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
1 => {
progress!(context, 1);
if param.addr.is_empty() {
error!(context, "Please enter an email address.",);
error!(context, 0, "Please enter an email address.",);
}
!param.addr.is_empty()
}
@@ -128,7 +146,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
true
} else {
error!(context, "Bad email-address.");
error!(context, 0, "Bad email-address.");
false
}
}
@@ -238,7 +256,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
12 => {
progress!(context, 500);
if let Some(ref cfg) = param_autoconfig {
info!(context, "Got autoconfig: {}", &cfg);
info!(context, 0, "Got autoconfig: {}", &cfg);
if !cfg.mail_user.is_empty() {
param.mail_user = cfg.mail_user.clone();
}
@@ -277,14 +295,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
}
}
if param.send_port == 0 {
param.send_port =
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
587
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
25
} else {
465
}
param.send_port = if 0 != param.server_flags & 0x10000 {
587
} else if 0 != param.server_flags & 0x40000 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
@@ -292,30 +309,24 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
param.server_flags |= DC_LP_AUTH_NORMAL as i32
if !dc_exactly_one_bit_set(param.server_flags & (0x2 | 0x4)) {
param.server_flags &= !(0x2 | 0x4);
param.server_flags |= 0x4
}
if !dc_exactly_one_bit_set(param.server_flags & (0x100 | 0x200 | 0x400)) {
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= if param.send_port == 143 { 0x100 } else { 0x200 }
}
if !dc_exactly_one_bit_set(
param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32,
param.server_flags & (0x10000 | 0x20000 | 0x40000),
) {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 143 {
DC_LP_IMAP_SOCKET_STARTTLS as i32
} else {
DC_LP_IMAP_SOCKET_SSL as i32
}
}
if !dc_exactly_one_bit_set(
param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32),
) {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= if param.send_port == 587 {
DC_LP_SMTP_SOCKET_STARTTLS as i32
0x10000
} else if param.send_port == 25 {
DC_LP_SMTP_SOCKET_PLAIN as i32
0x40000
} else {
DC_LP_SMTP_SOCKET_SSL as i32
0x20000
}
}
/* do we have a complete configuration? */
@@ -329,7 +340,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|| param.send_pw.is_empty()
|| param.server_flags == 0
{
error!(context, "Account settings incomplete.");
error!(context, 0, "Account settings incomplete.");
false
} else {
true
@@ -346,7 +357,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
ok_to_continue8 = true;
break;
}
info!(context, "Trying: {}", &param);
info!(context, 0, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -364,7 +375,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
progress!(context, 650 + username_variation * 30);
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= 0x100;
info!(context, "Trying: {}", &param);
info!(context, 0, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -377,7 +388,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
}
progress!(context, 660 + username_variation * 30);
param.mail_port = 143;
info!(context, "Trying: {}", &param);
info!(context, 0, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
@@ -429,10 +440,10 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
success = false;
} else {
progress!(context, 850);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 587;
info!(context, "Trying: {}", &param);
info!(context, 0, "Trying: {}", &param);
if !context
.smtp
@@ -445,10 +456,10 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
success = false;
} else {
progress!(context, 860);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 25;
info!(context, "Trying: {}", &param);
info!(context, 0, "Trying: {}", &param);
if !context
.smtp
@@ -507,7 +518,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
)
.ok();
context.sql.set_config_bool(context, "configured", true);
context.sql.set_config_int(context, "configured", 1).ok();
true
}
18 => {
@@ -517,13 +528,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
e2ee::ensure_secret_key_exists(context);
success = true;
info!(context, "Configure completed.");
info!(context, 0, "Configure completed.");
progress!(context, 940);
break; // We are done here
}
_ => {
error!(context, "Internal error: step counter out of bound",);
error!(context, 0, "Internal error: step counter out of bound",);
break;
}
};
@@ -562,7 +573,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
dc_free_ongoing(context);
}
progress!(context, if success { 1000 } else { 0 });
progress!(context, (if success { 1000 } else { 0 }));
}
/*******************************************************************************
@@ -571,7 +582,10 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
pub fn dc_alloc_ongoing(context: &Context) -> bool {
if dc_has_ongoing(context) {
warn!(context, "There is already another ongoing process running.",);
warn!(
context,
0, "There is already another ongoing process running.",
);
false
} else {
@@ -614,7 +628,7 @@ pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_
.unwrap_or_default()
== 0
{
warn!(context, "Not configured, cannot connect.",);
warn!(context, 0, "Not configured, cannot connect.",);
} else {
let param = LoginParam::from_database(context, "configured_");
// the trailing underscore is correct
@@ -637,15 +651,15 @@ pub fn dc_stop_ongoing_process(context: &Context) {
let mut s = s_a.write().unwrap();
if s.ongoing_running && !s.shall_stop_ongoing {
info!(context, "Signaling the ongoing process to stop ASAP.",);
info!(context, 0, "Signaling the ongoing process to stop ASAP.",);
s.shall_stop_ongoing = true;
} else {
info!(context, "No ongoing process to stop.",);
info!(context, 0, "No ongoing process to stop.",);
};
}
pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
info!(context, "Testing {} ...", url);
info!(context, 0, "Testing {} ...", url);
match reqwest::Client::new()
.get(url)
@@ -654,7 +668,7 @@ pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
{
Ok(res) => unsafe { res.strdup() },
Err(_err) => {
info!(context, "Can\'t read file.",);
info!(context, 0, "Can\'t read file.",);
std::ptr::null_mut()
}

View File

@@ -1,9 +1,10 @@
//! Constants
#![allow(non_camel_case_types, dead_code)]
use deltachat_derive::*;
use lazy_static::lazy_static;
use deltachat_derive::*;
lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
@@ -76,7 +77,7 @@ pub const DC_CHAT_ID_TRASH: u32 = 3;
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
pub const DC_CHAT_ID_STARRED: u32 = 5;
const DC_CHAT_ID_STARRED: u32 = 5;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
/// only an indicator in a chatlist
@@ -139,7 +140,7 @@ pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
const DC_LP_AUTH_NORMAL: usize = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -147,7 +148,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
@@ -159,19 +160,19 @@ pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
/// Connect to SMTP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// Connect to SMTP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is chosen
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
const DC_LP_IMAP_SOCKET_FLAGS: usize =
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
/// if none of these flags are set, the default is chosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
const DC_LP_SMTP_SOCKET_FLAGS: usize =
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
// QR code scanning (view from Bob, the joiner)
@@ -246,6 +247,242 @@ mod tests {
// If you do not want to handle an event, it is always safe to return 0,
// so there is no need to add a "case" for every event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum Event {
/// The library-user may write an informational string to the log.
/// Passed to the callback given to dc_context_new().
/// This event should not be reported to the end-user using a popup or something like that.
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
INFO = 100,
/// Emitted when SMTP connection is established and login was successful.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
SMTP_CONNECTED = 101,
/// Emitted when IMAP connection is established and login was successful.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
IMAP_CONNECTED = 102,
/// Emitted when a message was successfully sent to the SMTP server.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
SMTP_MESSAGE_SENT = 103,
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @param data1 0
/// @param data2 (const char*) Warning string in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
WARNING = 300,
/// The library-user should report an error to the end-user.
/// Passed to the callback given to dc_context_new().
///
/// As most things are asynchronous, things may go wrong at any time and the user
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
///
/// However, for ongoing processes (eg. configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
/// in a messasge box then.
///
/// @param data1 0
/// @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
/// localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
ERROR = 400,
/// An action cannot be performed because there is no network available.
///
/// The library will typically try over after a some time
/// and when dc_maybe_network() is called.
///
/// Network errors should be reported to users in a non-disturbing way,
/// however, as network errors may come in a sequence,
/// it is not useful to raise each an every error to the user.
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
///
/// Moreover, if the UI detects that the device is offline,
/// it is probably more useful to report this to the user
/// instead of the string from data2.
///
/// @param data1 (int) 1=first/new network error, should be reported the user;
/// 0=subsequent network error, should be logged only
/// @param data2 (const char*) Error string, always set, never NULL.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @return 0
ERROR_NETWORK = 401,
/// An action cannot be performed because the user is not in the group.
/// Reported eg. after a call to
/// dc_set_chat_name(), dc_set_chat_profile_image(),
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
/// dc_send_text_msg() or another sending function.
///
/// @param data1 0
/// @param data2 (const char*) Info string in english language.
/// Must not be free()'d or modified
/// and is valid only until the callback returns.
/// @return 0
ERROR_SELF_NOT_IN_GROUP = 410,
/// Messages or chats changed. One or more messages or chats changed for various
/// reasons in the database:
/// - Messages sent, received or removed
/// - Chats created, deleted or archived
/// - A draft has been set
///
/// @param data1 (int) chat_id for single added messages
/// @param data2 (int) msg_id for single added messages
/// @return 0
MSGS_CHANGED = 2000,
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
INCOMING_MSG = 2005,
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_DELIVERED = 2010,
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_FAILED = 2012,
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
///
/// @param data1 (int) chat_id
/// @param data2 (int) msg_id
/// @return 0
MSG_READ = 2015,
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
/// and dc_remove_contact_from_chat().
///
/// @param data1 (int) chat_id
/// @param data2 0
/// @return 0
CHAT_MODIFIED = 2020,
/// Contact(s) created, renamed, blocked or deleted.
///
/// @param data1 (int) If not 0, this is the contact_id of an added contact that should be selected.
/// @param data2 0
/// @return 0
CONTACTS_CHANGED = 2030,
/// Location of one or more contact has changed.
///
/// @param data1 (int) contact_id of the contact for which the location has changed.
/// If the locations of several contacts have been changed,
/// eg. after calling dc_delete_all_locations(), this parameter is set to 0.
/// @param data2 0
/// @return 0
LOCATION_CHANGED = 2035,
/// Inform about the configuration progress started by configure().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
CONFIGURE_PROGRESS = 2041,
/// Inform about the import/export progress started by dc_imex().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
IMEX_PROGRESS = 2051,
/// A file has been exported. A file has been written by dc_imex().
/// This event may be sent multiple times by a single call to dc_imex().
///
/// A typical purpose for a handler of this event may be to make the file public to some system
/// services.
///
/// @param data1 (const char*) Path and file name.
/// Must not be free()'d or modified and is valid only until the callback returns.
/// @param data2 0
/// @return 0
IMEX_FILE_WRITTEN = 2052,
/// Progress information of a secure-join handshake from the view of the inviter
/// (Alice, the person who shows the QR code).
///
/// These events are typically sent after a joiner has scanned the QR code
/// generated by dc_get_securejoin_qr().
///
/// @param data1 (int) ID of the contact that wants to join.
/// @param data2 (int) Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
/// @return 0
SECUREJOIN_INVITER_PROGRESS = 2060,
/// Progress information of a secure-join handshake from the view of the joiner
/// (Bob, the person who scans the QR code).
/// The events are typically sent while dc_join_securejoin(), which
/// may take some time, is executed.
/// @param data1 (int) ID of the inviting contact.
/// @param data2 (int) Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
/// @return 0
SECUREJOIN_JOINER_PROGRESS = 2061,
// the following events are functions that should be provided by the frontends
/// Requeste a localized string from the frontend.
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
/// the ui may use this value to return different strings on different plural forms.
/// @return (const char*) Null-terminated UTF-8 string.
/// The string will be free()'d by the core,
/// so it must be allocated using malloc() or a compatible function.
/// Return 0 if the ui cannot provide the requested string
/// the core will use a default string in english language then.
GET_STRING = 2091,
}
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
@@ -314,3 +551,12 @@ pub enum KeyType {
Public = 0,
Private = 1,
}
pub const DC_CMD_GROUPNAME_CHANGED: libc::c_int = 2;
pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
const DC_CMD_LOCATION_ONLY: libc::c_int = 9;

View File

@@ -1,5 +1,3 @@
use std::path::PathBuf;
use deltachat_derive::*;
use itertools::Itertools;
use rusqlite;
@@ -11,13 +9,13 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::events::Event;
use crate::key::*;
use crate::login_param::LoginParam;
use crate::message::MessageState;
use crate::peerstate::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
/// Contacts with at least this origin value are shown in the contact list.
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
@@ -32,8 +30,8 @@ const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
/// For this purpose, internally, two names are tracked -
/// authorized-name and given-name.
/// By default, these names are equal, but functions working with contact names
#[derive(Debug)]
pub struct Contact {
pub struct Contact<'a> {
context: &'a Context,
/// The contact ID.
///
/// Special message IDs:
@@ -140,10 +138,11 @@ pub enum VerifiedStatus {
BidirectVerified = 2,
}
impl Contact {
pub fn load_from_db(context: &Context, contact_id: u32) -> Result<Self> {
impl<'a> Contact<'a> {
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
let contact = Contact {
context,
id: contact_id,
name: context.stock_str(StockMessage::SelfMsg).into(),
authname: "".into(),
@@ -162,6 +161,7 @@ impl Contact {
params![contact_id as i32],
|row| {
let contact = Self {
context,
id: contact_id,
name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?,
@@ -180,7 +180,7 @@ impl Contact {
}
/// Check if a contact is blocked.
pub fn is_blocked_load(context: &Context, id: u32) -> bool {
pub fn is_blocked_load(context: &'a Context, id: u32) -> bool {
Self::load_from_db(context, id)
.map(|contact| contact.blocked)
.unwrap_or_default()
@@ -214,13 +214,15 @@ impl Contact {
let (contact_id, sth_modified) =
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?;
let blocked = Contact::is_blocked_load(context, contact_id);
context.call_cb(Event::ContactsChanged(
if sth_modified == Modifier::Created {
Some(contact_id)
context.call_cb(
Event::CONTACTS_CHANGED,
(if sth_modified == Modifier::Created {
contact_id
} else {
None
},
));
0
}) as uintptr_t,
0 as uintptr_t,
);
if blocked {
Contact::unblock(context, contact_id);
}
@@ -241,10 +243,7 @@ impl Contact {
)
.is_ok()
{
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0, 0);
}
}
@@ -267,7 +266,7 @@ impl Contact {
return 1;
}
context.sql.query_get_value(
context.sql.query_row_col(
context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
params![
@@ -275,6 +274,7 @@ impl Contact {
DC_CONTACT_ID_LAST_SPECIAL as i32,
DC_ORIGIN_MIN_CONTACT_LIST,
],
0
).unwrap_or_default()
}
@@ -307,6 +307,7 @@ impl Contact {
if !may_be_valid_addr(&addr) {
warn!(
context,
0,
"Bad address \"{}\" for contact \"{}\".",
addr,
if !name.as_ref().is_empty() {
@@ -402,7 +403,7 @@ impl Contact {
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
} else {
error!(context, "Cannot add contact.");
error!(context, 0, "Cannot add contact.");
}
}
@@ -437,7 +438,7 @@ impl Contact {
}
}
if modify_cnt > 0 {
context.call_cb(Event::ContactsChanged(None));
context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
}
Ok(modify_cnt)
@@ -540,10 +541,11 @@ impl Contact {
pub fn get_blocked_cnt(context: &Context) -> usize {
context
.sql
.query_get_value::<_, isize>(
.query_row_col::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -646,20 +648,22 @@ impl Contact {
let count_contacts: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
params![contact_id as i32],
0,
)
.unwrap_or_default();
let count_msgs: i32 = if count_contacts > 0 {
context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
params![contact_id as i32, contact_id as i32],
0,
)
.unwrap_or_default()
} else {
@@ -674,11 +678,11 @@ impl Contact {
params![contact_id as i32],
) {
Ok(_) => {
context.call_cb(Event::ContactsChanged(None));
context.call_cb(Event::CONTACTS_CHANGED, 0, 0);
return Ok(());
}
Err(err) => {
error!(context, "delete_contact {} failed ({})", contact_id, err);
error!(context, 0, "delete_contact {} failed ({})", contact_id, err);
return Err(err);
}
}
@@ -686,7 +690,7 @@ impl Contact {
info!(
context,
"could not delete contact {}, there are {} messages with it", contact_id, count_msgs
0, "could not delete contact {}, there are {} messages with it", contact_id, count_msgs
);
bail!("Could not delete contact with messages in it");
}
@@ -762,11 +766,9 @@ impl Contact {
/// Get the contact's profile image.
/// This is the image set by each remote user on their own
/// using dc_set_config(context, "selfavatar", image).
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
pub fn get_profile_image(&self) -> Option<String> {
if self.id == DC_CONTACT_ID_SELF {
if let Some(p) = context.get_config(Config::Selfavatar) {
return Some(PathBuf::from(p));
}
return self.context.get_config(Config::Selfavatar);
}
// TODO: else get image_abs from contact param
None
@@ -785,18 +787,14 @@ impl Contact {
///
/// The UI may draw a checkbox or something like that beside verified contacts.
///
pub fn is_verified(&self, context: &Context) -> VerifiedStatus {
self.is_verified_ex(context, None)
pub fn is_verified(&self) -> VerifiedStatus {
self.is_verified_ex(None)
}
/// Same as `Contact::is_verified` but allows speeding up things
/// by adding the peerstate belonging to the contact.
/// If you do not have the peerstate available, it is loaded automatically.
pub fn is_verified_ex(
&self,
context: &Context,
peerstate: Option<&Peerstate>,
) -> VerifiedStatus {
pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
if self.id == DC_CONTACT_ID_SELF {
@@ -809,7 +807,7 @@ impl Contact {
}
}
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
let peerstate = Peerstate::from_addr(self.context, &self.context.sql, &self.addr);
if let Some(ps) = peerstate {
if ps.verified_key().is_some() {
return VerifiedStatus::BidirectVerified;
@@ -843,10 +841,11 @@ impl Contact {
context
.sql
.query_get_value::<_, isize>(
.query_row_col::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>?;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -939,7 +938,11 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
params![new_blocking, 100, contact_id as i32],
).is_ok() {
Contact::mark_noticed(context, contact_id);
context.call_cb(Event::ContactsChanged(None));
context.call_cb(
Event::CONTACTS_CHANGED,
0,
0,
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use mmime::mailmime_content::*;
@@ -15,7 +14,6 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::events::Event;
use crate::job::*;
use crate::key::*;
use crate::message::*;
@@ -23,6 +21,7 @@ use crate::param::*;
use crate::pgp::*;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
// import/export and tools
@@ -30,16 +29,16 @@ use crate::x::*;
// param1 is a directory where the keys are searched in and read from
// param1 is a directory where the backup is written to
// param1 is the file with the backup to import
pub fn dc_imex(
pub unsafe fn dc_imex(
context: &Context,
what: libc::c_int,
param1: Option<impl AsRef<Path>>,
param1: *const libc::c_char,
param2: *const libc::c_char,
) {
let mut param = Params::new();
param.set_int(Param::Cmd, what as i32);
if let Some(param1) = param1 {
param.set(Param::Arg, param1.as_ref().to_string_lossy());
if !param1.is_null() {
param.set(Param::Arg, as_str(param1));
}
if !param2.is_null() {
param.set(Param::Arg2, as_str(param2));
@@ -52,13 +51,14 @@ pub fn dc_imex(
/// Returns the filename of the backup if found, nullptr otherwise.
pub unsafe fn dc_imex_has_backup(
context: &Context,
dir_name: impl AsRef<Path>,
dir_name: *const libc::c_char,
) -> *mut libc::c_char {
let dir_name = dir_name.as_ref();
let dir_name = as_path(dir_name);
let dir_iter = std::fs::read_dir(dir_name);
if dir_iter.is_err() {
info!(
context,
0,
"Backup check: Cannot open directory \"{}\".\x00",
dir_name.display(),
);
@@ -92,7 +92,7 @@ pub unsafe fn dc_imex_has_backup(
Some(path) => match path.to_c_string() {
Ok(cstr) => dc_strdup(cstr.as_ptr()),
Err(err) => {
error!(context, "Invalid backup filename: {}", err);
error!(context, 0, "Invalid backup filename: {}", err);
std::ptr::null_mut()
}
},
@@ -101,6 +101,7 @@ pub unsafe fn dc_imex_has_backup(
}
pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
let mut setup_file_name: *mut libc::c_char = ptr::null_mut();
let mut msg: Message;
if !dc_alloc_ongoing(context) {
return std::ptr::null_mut();
@@ -114,7 +115,8 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
if let Ok(ref setup_file_content) = dc_render_setup_file(context, &setup_code) {
if let Ok(setup_file_content) = dc_render_setup_file(context, &setup_code) {
let setup_file_content_c = CString::yolo(setup_file_content.as_str());
/* encrypting may also take a while ... */
if !context
.running_state
@@ -123,14 +125,23 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
let setup_file_name =
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
setup_file_name = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"autocrypt-setup-message.html\x00" as *const u8 as *const libc::c_char,
);
if !(setup_file_name.is_null()
|| 0 == dc_write_file(
context,
setup_file_name,
setup_file_content_c.as_ptr() as *const libc::c_void,
setup_file_content_c.as_bytes().len(),
))
{
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = dc_msg_new_untyped();
msg = dc_msg_new_untyped(context);
msg.type_0 = Viewtype::File;
msg.param
.set(Param::File, setup_file_name.to_string_lossy());
msg.param.set(Param::File, as_str(setup_file_name));
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
@@ -145,7 +156,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.shall_stop_ongoing
{
if let Ok(msg_id) = chat::send_msg(context, chat_id, &mut msg) {
info!(context, "Wait for setup message being sent ...",);
info!(context, 0, "Wait for setup message being sent ...",);
loop {
if context
.running_state
@@ -158,8 +169,8 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(msg) = dc_get_msg(context, msg_id) {
if dc_msg_is_sent(&msg) {
info!(context, "... setup message sent.",);
if 0 != dc_msg_is_sent(&msg) {
info!(context, 0, "... setup message sent.",);
break;
}
}
@@ -171,6 +182,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
}
}
free(setup_file_name as *mut libc::c_void);
dc_free_ongoing(context);
setup_code.strdup()
@@ -196,8 +208,16 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
_ => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
};
let private_key_asc = private_key.to_asc(ac_headers);
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
let encr = {
let private_key_asc_c = CString::yolo(private_key_asc);
let passphrase_c = CString::yolo(passphrase);
dc_pgp_symm_encrypt(
passphrase_c.as_ptr(),
private_key_asc_c.as_ptr() as *const libc::c_void,
private_key_asc_c.as_bytes().len(),
)
.ok_or(format_err!("Failed to encrypt private key."))?
};
let replacement = format!(
concat!(
"-----BEGIN PGP MESSAGE-----\r\n",
@@ -230,7 +250,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
}
pub fn dc_create_setup_code(_context: &Context) -> String {
let mut random_val: u16;
let mut random_val: uint16_t;
let mut rng = thread_rng();
let mut ret = String::new();
@@ -241,7 +261,7 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
break;
}
}
random_val = (random_val as libc::c_int % 10000) as u16;
random_val = (random_val as libc::c_int % 10000) as uint16_t;
ret += &format!(
"{}{:04}",
if 0 != i { "-" } else { "" },
@@ -252,54 +272,59 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
ret
}
// TODO should return bool /rtn
pub unsafe fn dc_continue_key_transfer(
context: &Context,
msg_id: u32,
msg_id: uint32_t,
setup_code: *const libc::c_char,
) -> bool {
let mut success = false;
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut filename: *mut libc::c_char = ptr::null_mut();
let mut filecontent: *mut libc::c_char = ptr::null_mut();
let mut filebytes: size_t = 0i32 as size_t;
let mut armored_key: *mut libc::c_char = ptr::null_mut();
let norm_sc;
if msg_id <= 9i32 as libc::c_uint || setup_code.is_null() {
return false;
}
let msg = dc_get_msg(context, msg_id);
if msg.is_err() {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
let msg = msg.unwrap();
if !dc_msg_is_setupmessage(&msg) {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
if let Some(filename) = dc_msg_get_file(context, &msg) {
if let Some(buf) = dc_read_file_safe(context, filename) {
let mut norm_sc: *mut libc::c_char = ptr::null_mut();
if !(msg_id <= 9i32 as libc::c_uint || setup_code.is_null()) {
let msg = dc_get_msg(context, msg_id);
if msg.is_err()
|| !dc_msg_is_setupmessage(msg.as_ref().unwrap())
|| {
filename = dc_msg_get_file(msg.as_ref().unwrap());
filename.is_null()
}
|| *filename.offset(0isize) as libc::c_int == 0i32
{
error!(context, 0, "Message is no Autocrypt Setup Message.",);
} else if 0
== dc_read_file(
context,
filename,
&mut filecontent as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut filebytes,
)
|| filecontent.is_null()
|| filebytes <= 0
{
error!(context, 0, "Cannot read Autocrypt Setup Message file.",);
} else {
norm_sc = dc_normalize_setup_code(context, setup_code);
if norm_sc.is_null() {
warn!(context, "Cannot normalize Setup Code.",);
warn!(context, 0, "Cannot normalize Setup Code.",);
} else {
armored_key = dc_decrypt_setup_file(context, norm_sc, buf.as_ptr().cast());
armored_key = dc_decrypt_setup_file(context, norm_sc, filecontent);
if armored_key.is_null() {
warn!(context, "Cannot decrypt Autocrypt Setup Message.",);
warn!(context, 0, "Cannot decrypt Autocrypt Setup Message.",);
} else if set_self_key(context, armored_key, 1) {
/*set default*/
/* error already logged */
success = true
success = 1i32
}
}
} else {
error!(context, "Cannot read Autocrypt Setup Message file.",);
return false;
}
} else {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
free(armored_key as *mut libc::c_void);
free(filecontent as *mut libc::c_void);
free(filename as *mut libc::c_void);
free(norm_sc as *mut libc::c_void);
success
@@ -318,7 +343,7 @@ fn set_self_key(
.and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
if keys.is_none() {
error!(context, "File does not contain a valid private key.",);
error!(context, 0, "File does not contain a valid private key.",);
return false;
}
@@ -348,13 +373,13 @@ fn set_self_key(
return false;
}
} else {
error!(context, "File does not contain a private key.",);
error!(context, 0, "File does not contain a private key.",);
}
let self_addr = context.get_config(Config::ConfiguredAddr);
let self_addr = context.sql.get_config(context, "configured_addr");
if self_addr.is_none() {
error!(context, "Missing self addr");
error!(context, 0, "Missing self addr");
return false;
}
@@ -366,7 +391,7 @@ fn set_self_key(
set_default,
&context.sql,
) {
error!(context, "Cannot save keypair.");
error!(context, 0, "Cannot save keypair.");
return false;
}
@@ -385,7 +410,7 @@ fn set_self_key(
}
pub unsafe fn dc_decrypt_setup_file(
context: &Context,
_context: &Context,
passphrase: *const libc::c_char,
filecontent: *const libc::c_char,
) -> *mut libc::c_char {
@@ -393,8 +418,8 @@ pub unsafe fn dc_decrypt_setup_file(
let mut fc_headerline: *const libc::c_char = ptr::null();
let mut fc_base64: *const libc::c_char = ptr::null();
let mut binary: *mut libc::c_char = ptr::null_mut();
let mut binary_bytes: libc::size_t = 0;
let mut indx: libc::size_t = 0;
let mut binary_bytes: size_t = 0i32 as size_t;
let mut indx: size_t = 0i32 as size_t;
let mut payload: *mut libc::c_char = ptr::null_mut();
fc_buf = dc_strdup(filecontent);
@@ -424,17 +449,11 @@ pub unsafe fn dc_decrypt_setup_file(
|| binary_bytes == 0)
{
/* decrypt symmetrically */
match dc_pgp_symm_decrypt(
as_str(passphrase),
std::slice::from_raw_parts(binary as *const u8, binary_bytes),
) {
Ok(plain) => {
let payload_c = CString::new(plain).unwrap();
payload = strdup(payload_c.as_ptr());
}
Err(err) => {
error!(context, "Failed to decrypt message: {:?}", err);
}
if let Some(plain) =
dc_pgp_symm_decrypt(passphrase, binary as *const libc::c_void, binary_bytes)
{
let payload_c = CString::new(plain).unwrap();
payload = strdup(payload_c.as_ptr());
}
}
}
@@ -483,27 +502,28 @@ pub unsafe fn dc_normalize_setup_code(
pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
let mut ok_to_continue = true;
let mut success: libc::c_int = 0;
let what: libc::c_int;
if dc_alloc_ongoing(context) {
let what = job.param.get_int(Param::Cmd).unwrap_or_default();
what = job.param.get_int(Param::Cmd).unwrap_or_default();
let param1_s = job.param.get(Param::Arg).unwrap_or_default();
let param1 = CString::yolo(param1_s);
let _param2 = CString::yolo(job.param.get(Param::Arg2).unwrap_or_default());
if strlen(param1.as_ptr()) == 0 {
error!(context, "No Import/export dir/file given.",);
error!(context, 0, "No Import/export dir/file given.",);
} else {
info!(context, "Import/export process started.",);
context.call_cb(Event::ImexProgress(10));
info!(context, 0, "Import/export process started.",);
context.call_cb(Event::IMEX_PROGRESS, 10 as uintptr_t, 0 as uintptr_t);
if !context.sql.is_open() {
error!(context, "Import/export: Database not opened.",);
error!(context, 0, "Import/export: Database not opened.",);
} else {
if what == 1 || what == 11 {
/* before we export anything, make sure the private key exists */
if e2ee::ensure_secret_key_exists(context).is_err() {
error!(
context,
0,
"Import/export: Cannot create private key or private key not available.",
);
ok_to_continue = false;
@@ -514,26 +534,26 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
if ok_to_continue {
match what {
1 => {
if export_self_keys(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
if 0 != export_self_keys(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
2 => {
if 0 != import_self_keys(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
info!(context, 0, "Import/export completed.",);
success = 1
}
}
11 => {
if export_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
if 0 != export_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
12 => {
if import_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
if 0 != import_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
@@ -544,52 +564,68 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
}
dc_free_ongoing(context);
}
context.call_cb(Event::ImexProgress(if 0 != success { 1000 } else { 0 }));
context.call_cb(
Event::IMEX_PROGRESS,
(if 0 != success { 1000 } else { 0 }) as uintptr_t,
0 as uintptr_t,
);
}
/*******************************************************************************
* Import backup
******************************************************************************/
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> bool {
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> libc::c_int {
info!(
context,
0,
"Import \"{}\" to \"{}\".",
as_str(backup_to_import),
context.get_dbfile().display()
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap())
);
if dc_is_configured(context) {
error!(context, "Cannot import backups to accounts in use.");
return false;
if 0 != dc_is_configured(context) {
error!(context, 0, "Cannot import backups to accounts in use.");
return 0;
}
&context.sql.close(&context);
dc_delete_file(context, context.get_dbfile());
if dc_file_exist(context, context.get_dbfile()) {
dc_delete_file(context, context.get_dbfile().unwrap());
if dc_file_exist(context, context.get_dbfile().unwrap()) {
error!(
context,
"Cannot import backups: Cannot delete the old file.",
0, "Cannot import backups: Cannot delete the old file.",
);
return false;
return 0;
}
if !dc_copy_file(context, as_path(backup_to_import), context.get_dbfile()) {
return false;
if !dc_copy_file(
context,
as_path(backup_to_import),
context.get_dbfile().unwrap(),
) {
return 0;
}
/* error already logged */
/* re-open copied database file */
if !context.sql.open(&context, &context.get_dbfile(), 0) {
return false;
if !context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0)
{
return 0;
}
let total_files_cnt = context
.sql
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
.query_row_col::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![], 0)
.unwrap_or_default() as usize;
info!(
context,
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
0, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
);
let res = context.sql.query_map(
@@ -626,19 +662,20 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
if permille > 990 {
permille = 990
}
context.call_cb(Event::ImexProgress(permille));
context.call_cb(Event::IMEX_PROGRESS, permille as uintptr_t, 0);
if file_blob.is_empty() {
continue;
}
let pathNfilename = context.get_blobdir().join(file_name);
if dc_write_file(context, &pathNfilename, &file_blob) {
let pathNfilename = format!("{}/{}", as_str(context.get_blobdir()), file_name);
if dc_write_file_safe(context, &pathNfilename, &file_blob) {
continue;
}
error!(
context,
0,
"Storage full? Cannot write file {} with {} bytes.",
pathNfilename.display(),
&pathNfilename,
file_blob.len(),
);
// otherwise the user may believe the stuff is imported correctly, but there are files missing ...
@@ -659,7 +696,7 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
sql::try_execute(context, &context.sql, "VACUUM;").ok();
Ok(())
})
.is_ok()
.is_ok() as libc::c_int
}
/*******************************************************************************
@@ -667,10 +704,11 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
******************************************************************************/
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
The macro avoids weird values of 0% or 100% while still working. */
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
let mut ok_to_continue: bool;
let mut success = false;
let mut success: libc::c_int = 0;
let mut delete_dest_file: libc::c_int = 0;
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
@@ -679,8 +717,13 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
.format("delta-chat-%Y-%m-%d.bak")
.to_string();
let buffer = CString::yolo(res);
let dest_pathNfilename = dc_get_fine_pathNfilename(context, dir, buffer.as_ptr());
if dest_pathNfilename.is_null() {
error!(context, 0, "Cannot get backup file name.",);
let dest_path_filename = dc_get_fine_path_filename(context, as_path(dir), res);
return success;
}
sql::housekeeping(context);
@@ -689,17 +732,27 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
let mut closed = true;
info!(
context,
0,
"Backup \"{}\" to \"{}\".",
context.get_dbfile().display(),
dest_path_filename.display(),
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap()),
as_str(dest_pathNfilename),
);
if dc_copy_file(context, context.get_dbfile(), &dest_path_filename) {
context.sql.open(&context, &context.get_dbfile(), 0);
if dc_copy_file(
context,
context.get_dbfile().unwrap(),
as_path(dest_pathNfilename),
) {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
closed = false;
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
let sql = Sql::new();
if sql.open(context, &dest_path_filename, 0) {
if sql.open(context, as_path(dest_pathNfilename), 0) {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
@@ -719,14 +772,14 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
let dir = std::path::Path::new(as_str(context.get_blobdir()));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
info!(context, 0, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
if let Ok(dir_handle) = std::fs::read_dir(dir) {
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
@@ -757,7 +810,11 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
if permille > 990 {
permille = 990;
}
context.call_cb(Event::ImexProgress(permille));
context.call_cb(
Event::IMEX_PROGRESS,
permille as uintptr_t,
0 as uintptr_t,
);
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
@@ -765,8 +822,13 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
{
continue;
} else {
info!(context, "EXPORTing filename={}", name);
let curr_pathNfilename = context.get_blobdir().join(entry.file_name());
info!(context, 0, "EXPORTing filename={}", name);
let curr_pathNfilename = format!(
"{}/{}",
as_str(context.get_blobdir()),
name
);
if let Some(buf) =
dc_read_file_safe(context, &curr_pathNfilename)
{
@@ -776,8 +838,9 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
if stmt.execute(params![name, buf]).is_err() {
error!(
context,
0,
"Disk full? Cannot add file \"{}\" to backup.",
curr_pathNfilename.display(),
&curr_pathNfilename,
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
@@ -795,12 +858,13 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
context.get_blobdir().display(),
as_str(context.get_blobdir()),
);
}
} else {
info!(context, "Backup: No files to copy.",);
info!(context, 0, "Backup: No files to copy.",);
ok_to_continue = true;
}
if ok_to_continue {
@@ -808,26 +872,34 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
success = true;
context.call_cb(
Event::IMEX_FILE_WRITTEN,
dest_pathNfilename as uintptr_t,
0,
);
success = 1;
}
}
} else {
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
context.get_blobdir().display(),
as_str(context.get_blobdir())
);
};
}
}
}
if closed {
context.sql.open(&context, &context.get_dbfile(), 0);
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
}
if 0 != delete_dest_file {
dc_delete_file(context, &dest_path_filename);
dc_delete_file(context, as_path(dest_pathNfilename));
}
free(dest_pathNfilename as *mut libc::c_void);
success
}
@@ -844,8 +916,10 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
let mut imported_cnt: libc::c_int = 0;
let mut suffix: *mut libc::c_char = ptr::null_mut();
let mut path_plus_name: *mut libc::c_char = ptr::null_mut();
let mut set_default: libc::c_int;
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut buf_bytes: size_t = 0 as size_t;
// a pointer inside buf, MUST NOT be free()'d
let mut private_key: *const libc::c_char;
let mut buf2: *mut libc::c_char = ptr::null_mut();
@@ -868,21 +942,25 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
{
continue;
}
let path_plus_name = dir.join(entry.file_name());
info!(context, "Checking: {}", path_plus_name.display());
free(buf.cast());
free(path_plus_name as *mut libc::c_void);
path_plus_name = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
dir_name,
name_c.as_ptr(),
);
info!(context, 0, "Checking: {}", as_str(path_plus_name));
free(buf as *mut libc::c_void);
buf = ptr::null_mut();
if let Some(buf_r) = dc_read_file_safe(context, &path_plus_name) {
buf = buf_r.as_ptr() as *mut _;
std::mem::forget(buf_r);
} else {
if 0 == dc_read_file(
context,
path_plus_name,
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut buf_bytes,
) || buf_bytes < 50
{
continue;
};
}
private_key = buf;
free(buf2 as *mut libc::c_void);
buf2 = dc_strdup(buf);
if dc_split_armored_data(
@@ -914,8 +992,9 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
{
info!(
context,
0,
"Treating \"{}\" as a legacy private key.",
path_plus_name.display(),
as_str(path_plus_name),
);
set_default = 0i32
}
@@ -927,6 +1006,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
if imported_cnt == 0i32 {
error!(
context,
0,
"No private keys found in \"{}\".",
as_str(dir_name),
);
@@ -934,6 +1014,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
} else {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
@@ -941,13 +1022,15 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
}
free(suffix as *mut libc::c_void);
free(path_plus_name as *mut libc::c_void);
free(buf as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
imported_cnt
}
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool {
// TODO should return bool /rtn
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc::c_int {
let mut export_errors = 0;
context
@@ -969,14 +1052,14 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?;
if let Some(key) = public_key {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
@@ -989,45 +1072,61 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
)
.unwrap();
export_errors == 0
if export_errors == 0 {
1
} else {
0
}
}
/*******************************************************************************
* Classic key export
******************************************************************************/
// TODO should return bool /rtn
unsafe fn export_key_to_asc_file(
context: &Context,
dir: *const libc::c_char,
id: libc::c_int,
key: &Key,
is_default: libc::c_int,
) -> bool {
let mut success = false;
let dir = as_path(dir);
let file_name = if 0 != is_default {
let name = format!(
"{}-key-default.asc",
if key.is_public() { "public" } else { "private" },
);
dir.join(name)
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let file_name;
if 0 != is_default {
file_name = dc_mprintf(
b"%s/%s-key-default.asc\x00" as *const u8 as *const libc::c_char,
dir,
if key.is_public() {
b"public\x00" as *const u8 as *const libc::c_char
} else {
b"private\x00" as *const u8 as *const libc::c_char
},
)
} else {
let name = format!(
"{}-key-{}.asc",
if key.is_public() { "public" } else { "private" },
id
);
dir.join(name)
};
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name);
if !key.write_asc_to_file(&file_name, context) {
error!(context, "Cannot write key to {}", file_name.display());
} else {
context.call_cb(Event::ImexFileWritten(file_name.clone()));
success = true;
file_name = dc_mprintf(
b"%s/%s-key-%i.asc\x00" as *const u8 as *const libc::c_char,
dir,
if key.is_public() {
b"public\x00" as *const u8 as *const libc::c_char
} else {
b"private\x00" as *const u8 as *const libc::c_char
},
id,
)
}
info!(context, 0, "Exporting key {}", as_str(file_name),);
dc_delete_file(context, as_path(file_name));
if !key.write_asc_to_file(file_name, context) {
error!(context, 0, "Cannot write key to {}", as_str(file_name),);
} else {
context.call_cb(
Event::IMEX_FILE_WRITTEN,
file_name as uintptr_t,
0i32 as uintptr_t,
);
success = 1i32
}
free(file_name as *mut libc::c_void);
success
}
@@ -1036,11 +1135,13 @@ unsafe fn export_key_to_asc_file(
mod tests {
use super::*;
use num_traits::ToPrimitive;
use crate::test_utils::*;
#[test]
fn test_render_setup_file() {
let t = test_context(Some(Box::new(logging_cb)));
let t = test_context(Some(logging_cb));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
@@ -1058,19 +1159,22 @@ mod tests {
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t {
match evt {
Event::GetString {
id: StockMessage::AcSetupMsgBody,
..
} => unsafe { "hello\r\nthere".strdup() as usize },
_ => logging_cb(ctx, evt),
unsafe extern "C" fn ac_setup_msg_cb(
ctx: &Context,
evt: Event,
d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() {
"hello\r\nthere".strdup() as usize
} else {
logging_cb(ctx, evt, d1, d2)
}
}
#[test]
fn test_render_setup_file_newline_replace() {
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
let t = test_context(Some(ac_setup_msg_cb));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);

View File

@@ -1,8 +1,7 @@
use std::path::Path;
use std::ffi::CString;
use std::ptr;
use chrono::TimeZone;
use mmime::clist::*;
use mmime::mailimf_types::*;
use mmime::mailimf_types_helper::*;
use mmime::mailmime_disposition::*;
@@ -15,8 +14,7 @@ use mmime::other::*;
use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::SystemMessage;
use crate::context::{dc_get_version_str, Context};
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -25,6 +23,7 @@ use crate::location;
use crate::message::*;
use crate::param::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
#[derive(Clone)]
@@ -32,22 +31,22 @@ use crate::x::*;
pub struct dc_mimefactory_t<'a> {
pub from_addr: *mut libc::c_char,
pub from_displayname: *mut libc::c_char,
pub selfstatus: Option<String>,
pub selfstatus: *mut libc::c_char,
pub recipients_names: *mut clist,
pub recipients_addr: *mut clist,
pub timestamp: i64,
pub rfc724_mid: *mut libc::c_char,
pub loaded: dc_mimefactory_loaded_t,
pub msg: Message,
pub chat: Option<Chat>,
pub increation: bool,
pub msg: Message<'a>,
pub chat: Option<Chat<'a>>,
pub increation: libc::c_int,
pub in_reply_to: *mut libc::c_char,
pub references: *mut libc::c_char,
pub req_mdn: libc::c_int,
pub out: *mut MMAPString,
pub out_encrypted: libc::c_int,
pub out_gossiped: libc::c_int,
pub out_last_added_location_id: u32,
pub out_last_added_location_id: uint32_t,
pub error: *mut libc::c_char,
pub context: &'a Context,
}
@@ -57,6 +56,7 @@ impl<'a> Drop for dc_mimefactory_t<'a> {
unsafe {
free(self.from_addr as *mut libc::c_void);
free(self.from_displayname as *mut libc::c_void);
free(self.selfstatus as *mut libc::c_void);
free(self.rfc724_mid as *mut libc::c_void);
if !self.recipients_names.is_null() {
clist_free_content(self.recipients_names);
@@ -94,7 +94,7 @@ pub unsafe fn dc_mimefactory_load_msg(
let mut factory = dc_mimefactory_t {
from_addr: ptr::null_mut(),
from_displayname: ptr::null_mut(),
selfstatus: None,
selfstatus: ptr::null_mut(),
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
@@ -102,7 +102,7 @@ pub unsafe fn dc_mimefactory_load_msg(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: Some(chat),
increation: false,
increation: 0,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -170,10 +170,10 @@ pub unsafe fn dc_mimefactory_load_msg(
)
.unwrap();
let command = factory.msg.param.get_cmd();
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
let msg = &factory.msg;
if command == SystemMessage::MemberRemovedFromGroup {
if command == 5 {
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
let email_to_remove_c = email_to_remove.strdup();
@@ -197,8 +197,8 @@ pub unsafe fn dc_mimefactory_load_msg(
}
}
}
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
if command != 6
&& command != 7
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
@@ -225,7 +225,7 @@ pub unsafe fn dc_mimefactory_load_msg(
Err(err) => {
error!(
context,
"mimefactory: failed to load mime_in_reply_to: {:?}", err
0, "mimefactory: failed to load mime_in_reply_to: {:?}", err
);
}
}
@@ -252,21 +252,19 @@ unsafe fn load_from(factory: &mut dc_mimefactory_t) {
.unwrap_or_default()
.strdup();
factory.selfstatus = context.sql.get_config(context, "selfstatus");
if factory.selfstatus.is_none() {
factory.selfstatus = Some(
factory
.context
.stock_str(StockMessage::StatusLine)
.to_string(),
);
}
factory.selfstatus = context
.sql
.get_config(context, "selfstatus")
.unwrap_or_default()
.strdup();
if factory.selfstatus.is_null() {
factory.selfstatus = factory.context.stock_str(StockMessage::StatusLine).strdup();
};
}
pub unsafe fn dc_mimefactory_load_mdn<'a>(
context: &'a Context,
msg_id: u32,
msg_id: uint32_t,
) -> Result<dc_mimefactory_t, Error> {
if 0 == context
.sql
@@ -284,7 +282,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
let mut factory = dc_mimefactory_t {
from_addr: ptr::null_mut(),
from_displayname: ptr::null_mut(),
selfstatus: None,
selfstatus: ptr::null_mut(),
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
@@ -292,7 +290,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: None,
increation: false,
increation: 0,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -336,16 +334,18 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
Ok(factory)
}
pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefactory_t) -> bool {
// TODO should return bool /rtn
pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_int {
let subject: *mut mailimf_subject;
let mut ok_to_continue = true;
let imf_fields: *mut mailimf_fields;
let mut message: *mut mailmime = ptr::null_mut();
let mut message_text: *mut libc::c_char = ptr::null_mut();
let mut message_text2: *mut libc::c_char = ptr::null_mut();
let mut subject_str: *mut libc::c_char = ptr::null_mut();
let mut afwd_email: libc::c_int = 0;
let mut col: libc::c_int = 0;
let mut success = false;
let mut success: libc::c_int = 0;
let mut parts: libc::c_int = 0;
let mut e2ee_guaranteed: libc::c_int = 0;
let mut min_verified: libc::c_int = 0;
@@ -369,7 +369,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
from,
mailimf_mailbox_new(
if !factory.from_displayname.is_null() {
dc_encode_header_words(as_str(factory.from_displayname))
dc_encode_header_words(factory.from_displayname)
} else {
ptr::null_mut()
},
@@ -403,7 +403,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_null() {
dc_encode_header_words(as_str(name))
dc_encode_header_words(name)
} else {
ptr::null_mut()
},
@@ -458,14 +458,20 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
.as_ref()
.map(|s| format!("/{}", s))
.unwrap_or_default();
let version = get_version_str();
let os_part = CString::new(os_part).expect("String -> CString conversion failed");
let version = dc_get_version_str();
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
strdup(b"X-Mailer\x00" as *const u8 as *const libc::c_char),
format!("Delta Chat Core {}{}", version, os_part).strdup(),
dc_mprintf(
b"Delta Chat Core %s%s\x00" as *const u8 as *const libc::c_char,
version,
os_part.as_ptr(),
),
),
);
free(version.cast());
mailimf_fields_add(
imf_fields,
@@ -492,8 +498,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
*********************************************************************/
let chat = factory.chat.as_ref().unwrap();
let mut meta_part: *mut mailmime = ptr::null_mut();
let mut placeholdertext = None;
let mut placeholdertext: *mut libc::c_char = ptr::null_mut();
if chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -526,7 +531,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
/* build header etc. */
let command = factory.msg.param.get_cmd();
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -535,15 +540,15 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
chat.grpid.strdup(),
),
);
let name = CString::yolo(chat.name.as_bytes());
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
strdup(b"Chat-Group-Name\x00" as *const u8 as *const libc::c_char),
dc_encode_header_words(&chat.name),
dc_encode_header_words(name.as_ptr()),
),
);
if command == SystemMessage::MemberRemovedFromGroup {
if command == 5 {
let email_to_remove = factory
.msg
.param
@@ -562,7 +567,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
),
);
}
} else if command == SystemMessage::MemberAddedToGroup {
} else if command == 4 {
let msg = &factory.msg;
do_gossip = 1;
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -581,7 +586,8 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 {
info!(
context,
msg.context,
0,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
"vg-member-added",
);
@@ -593,7 +599,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
),
);
}
} else if command == SystemMessage::GroupNameChanged {
} else if command == 2 {
let msg = &factory.msg;
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -606,7 +612,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
value_to_add,
),
);
} else if command == SystemMessage::GroupImageChanged {
} else if command == 3 {
let msg = &factory.msg;
grpimage = msg.param.get(Param::Arg);
if grpimage.is_none() {
@@ -620,8 +626,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
}
}
if command == SystemMessage::LocationStreamingEnabled {
if command == 8 {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -632,7 +637,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
),
);
}
if command == SystemMessage::AutocryptSetupMessage {
if command == 6 {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -640,19 +645,18 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
strdup(b"v1\x00" as *const u8 as *const libc::c_char),
),
);
placeholdertext = Some(
factory
.context
.stock_str(StockMessage::AcSetupMsgBody)
.to_string(),
);
placeholdertext = factory
.context
.stock_str(StockMessage::AcSetupMsgBody)
.strdup();
}
if command == SystemMessage::SecurejoinMessage {
if command == 7 {
let msg = &factory.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
if strlen(step) > 0 {
info!(
context,
msg.context,
0,
"sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>",
as_str(step),
);
@@ -721,16 +725,13 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
}
}
if let Some(grpimage) = grpimage {
info!(factory.context, "setting group image '{}'", grpimage);
let mut meta = dc_msg_new_untyped();
let mut meta = dc_msg_new_untyped(factory.context);
meta.type_0 = Viewtype::Image;
meta.param.set(Param::File, grpimage);
let mut filename_as_sent = ptr::null_mut();
meta_part = build_body_file(
context,
&meta,
b"group-image\x00" as *const u8 as *const libc::c_char,
&mut filename_as_sent,
@@ -769,72 +770,82 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
imf_fields,
mailimf_field_new_custom(
strdup(b"Chat-Duration\x00" as *const u8 as *const libc::c_char),
duration_ms.to_string().strdup(),
dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
duration_ms as libc::c_int,
),
),
);
}
}
afwd_email = factory.msg.param.exists(Param::Forwarded) as libc::c_int;
let fwdhint = if 0 != afwd_email {
Some(
"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n"
.to_string(),
let mut fwdhint = ptr::null_mut();
if 0 != afwd_email {
fwdhint = dc_strdup(
b"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n\x00"
as *const u8 as *const libc::c_char,
)
} else {
None
};
}
let final_text = {
if let Some(ref text) = placeholdertext {
text
if !placeholdertext.is_null() {
to_string(placeholdertext)
} else if let Some(ref text) = factory.msg.text {
text
text.clone()
} else {
""
"".into()
}
};
let final_text = CString::yolo(final_text);
let footer = factory.selfstatus.as_ref();
message_text = format!(
"{}{}{}{}{}",
if let Some(ref hint) = fwdhint {
hint
let footer: *mut libc::c_char = factory.selfstatus;
message_text = dc_mprintf(
b"%s%s%s%s%s\x00" as *const u8 as *const libc::c_char,
if !fwdhint.is_null() {
fwdhint
} else {
""
b"\x00" as *const u8 as *const libc::c_char
},
&final_text,
if !final_text.is_empty() && footer.is_some() {
"\r\n\r\n"
final_text.as_ptr(),
if final_text != CString::yolo("")
&& !footer.is_null()
&& 0 != *footer.offset(0isize) as libc::c_int
{
b"\r\n\r\n\x00" as *const u8 as *const libc::c_char
} else {
""
b"\x00" as *const u8 as *const libc::c_char
},
if footer.is_some() { "-- \r\n" } else { "" },
if let Some(footer) = footer {
if !footer.is_null() && 0 != *footer.offset(0isize) as libc::c_int {
b"-- \r\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !footer.is_null() && 0 != *footer.offset(0isize) as libc::c_int {
footer
} else {
""
b"\x00" as *const u8 as *const libc::c_char
},
)
.strdup();
);
let text_part: *mut mailmime = build_body_text(message_text);
mailmime_smart_add_part(message, text_part);
parts += 1;
free(fwdhint as *mut libc::c_void);
free(placeholdertext as *mut libc::c_void);
/* add attachment part */
if chat::msgtype_has_file(factory.msg.type_0) {
if !is_file_size_okay(context, &factory.msg) {
let error = format!(
"Message exceeds the recommended {} MB.",
if !is_file_size_okay(&factory.msg) {
let error: *mut libc::c_char = dc_mprintf(
b"Message exceeds the recommended %i MB.\x00" as *const u8
as *const libc::c_char,
24 * 1024 * 1024 / 4 * 3 / 1000 / 1000,
)
.strdup();
);
set_error(factory, error);
free(error.cast());
free(error as *mut libc::c_void);
ok_to_continue = false;
} else {
let file_part: *mut mailmime =
build_body_file(context, &factory.msg, ptr::null(), ptr::null_mut());
build_body_file(&factory.msg, ptr::null(), ptr::null_mut());
if !file_part.is_null() {
mailmime_smart_add_part(message, file_part);
parts += 1
@@ -882,9 +893,12 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
mailmime_smart_add_part(message, kml_mime_part);
}
if location::is_sending_locations_to_chat(context, factory.msg.chat_id) {
if location::is_sending_locations_to_chat(
factory.msg.context,
factory.msg.chat_id,
) {
if let Ok((kml_file, last_added_location_id)) =
location::get_kml(context, factory.msg.chat_id)
location::get_kml(factory.msg.context, factory.msg.chat_id)
{
let content_type = mailmime_content_new_with_str(
b"application/vnd.google-earth.kml+xml\x00" as *const u8
@@ -937,7 +951,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
.stock_str(StockMessage::EncryptedMsg)
.into_owned()
} else {
to_string(dc_msg_get_summarytext(context, &mut factory.msg, 32))
to_string(dc_msg_get_summarytext(&mut factory.msg, 32))
};
let p2 = factory
.context
@@ -945,15 +959,16 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
message_text = format!("{}\r\n", p2).strdup();
let human_mime_part: *mut mailmime = build_body_text(message_text);
mailmime_add_part(multipart, human_mime_part);
let version = get_version_str();
message_text2 = format!(
"Reporting-UA: Delta Chat {}\r\nOriginal-Recipient: rfc822;{}\r\nFinal-Recipient: rfc822;{}\r\nOriginal-Message-ID: <{}>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n",
version,
as_str(factory.from_addr),
as_str(factory.from_addr),
as_str(factory.msg.rfc724_mid)
).strdup();
let version = dc_get_version_str();
message_text2 =
dc_mprintf(
b"Reporting-UA: Delta Chat %s\r\nOriginal-Recipient: rfc822;%s\r\nFinal-Recipient: rfc822;%s\r\nOriginal-Message-ID: <%s>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n\x00"
as *const u8 as *const libc::c_char,
version,
factory.from_addr, factory.from_addr,
factory.msg.rfc724_mid
);
free(version.cast());
let content_type_0: *mut mailmime_content = mailmime_content_new_with_str(
b"message/disposition-notification\x00" as *const u8 as *const libc::c_char,
);
@@ -972,19 +987,16 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
if ok_to_continue {
let subject_str = if factory.loaded as libc::c_uint == DC_MF_MDN_LOADED as libc::c_uint
{
let e = factory.context.stock_str(StockMessage::ReadRcpt);
format!("Chat: {}", e)
if factory.loaded as libc::c_uint == DC_MF_MDN_LOADED as libc::c_int as libc::c_uint {
let e = CString::new(factory.context.stock_str(StockMessage::ReadRcpt).as_ref())
.unwrap();
subject_str = dc_mprintf(
b"Chat: %s\x00" as *const u8 as *const libc::c_char,
e.as_ptr(),
);
} else {
to_string(get_subject(
context,
factory.chat.as_ref(),
&mut factory.msg,
afwd_email,
))
};
subject_str = get_subject(factory.chat.as_ref(), &mut factory.msg, afwd_email)
}
subject = mailimf_subject_new(dc_encode_header_words(subject_str));
mailimf_fields_add(
imf_fields,
@@ -1033,7 +1045,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
}
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
mailmime_write_mem(factory.out, &mut col, message);
success = true;
success = 1;
}
}
@@ -1043,12 +1055,12 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
e2ee_helper.thanks();
free(message_text as *mut libc::c_void);
free(message_text2 as *mut libc::c_void);
free(subject_str as *mut libc::c_void);
success
}
unsafe fn get_subject(
context: &Context,
chat: Option<&Chat>,
msg: &mut Message,
afwd_email: libc::c_int,
@@ -1058,19 +1070,37 @@ unsafe fn get_subject(
}
let chat = chat.unwrap();
let context = chat.context;
let ret: *mut libc::c_char;
let raw_subject =
dc_msg_get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context);
let raw_subject = {
dc_msg_get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context)
.strdup()
};
let fwd = if 0 != afwd_email { "Fwd: " } else { "" };
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
let fwd = if 0 != afwd_email {
b"Fwd: \x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
};
if msg.param.get_int(Param::Cmd).unwrap_or_default() == 6 {
ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
ret = format!("Chat: {}: {}{}", chat.name, fwd, raw_subject,).strdup();
ret = format!(
"Chat: {}: {}{}",
chat.name,
to_string(fwd),
to_string(raw_subject),
)
.strdup()
} else {
ret = format!("Chat: {}{}", fwd, raw_subject).strdup();
ret = dc_mprintf(
b"Chat: %s%s\x00" as *const u8 as *const libc::c_char,
fwd,
raw_subject,
)
}
free(raw_subject as *mut libc::c_void);
ret
}
@@ -1105,7 +1135,6 @@ unsafe fn build_body_text(text: *mut libc::c_char) -> *mut mailmime {
#[allow(non_snake_case)]
unsafe fn build_body_file(
context: &Context,
msg: &Message,
mut base_name: *const libc::c_char,
ret_file_name_as_sent: *mut *mut libc::c_char,
@@ -1122,12 +1151,13 @@ unsafe fn build_body_file(
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
let mut filename_to_send = ptr::null_mut();
let mut filename_encoded = ptr::null_mut();
if let Some(ref path_filename) = path_filename {
let suffix = dc_get_filesuffix_lc(path_filename);
let filename_to_send = if msg.type_0 == Viewtype::Voice {
if msg.type_0 == Viewtype::Voice {
let ts = chrono::Utc.timestamp(msg.timestamp_sort as i64, 0);
let suffix = if !suffix.is_null() {
@@ -1135,42 +1165,37 @@ unsafe fn build_body_file(
} else {
"dat".into()
};
ts.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string()
let res = ts
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string();
filename_to_send = res.strdup();
} else if msg.type_0 == Viewtype::Audio {
Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default()
filename_to_send = dc_get_filename(path_filename)
} else if msg.type_0 == Viewtype::Image || msg.type_0 == Viewtype::Gif {
if base_name.is_null() {
base_name = b"image\x00" as *const u8 as *const libc::c_char
}
format!(
"{}.{}",
as_str(base_name),
filename_to_send = dc_mprintf(
b"%s.%s\x00" as *const u8 as *const libc::c_char,
base_name,
if !suffix.is_null() {
as_str(suffix)
suffix
} else {
"dat"
b"dat\x00" as *const u8 as *const libc::c_char
},
)
} else if msg.type_0 == Viewtype::Video {
format!(
"video.{}",
filename_to_send = dc_mprintf(
b"video.%s\x00" as *const u8 as *const libc::c_char,
if !suffix.is_null() {
as_str(suffix)
suffix
} else {
"dat"
b"dat\x00" as *const u8 as *const libc::c_char
},
)
} else {
Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default()
};
filename_to_send = dc_get_filename(path_filename)
}
if mimetype.is_null() {
if suffix.is_null() {
mimetype =
@@ -1193,13 +1218,13 @@ unsafe fn build_body_file(
/* create mime part, for Content-Disposition, see RFC 2183.
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
needs_ext = dc_needs_ext_header(&filename_to_send);
needs_ext = dc_needs_ext_header(as_str(filename_to_send));
mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
if needs_ext {
ptr::null_mut()
} else {
filename_to_send.strdup()
dc_strdup(filename_to_send)
},
MAILMIME_MECHANISM_BASE64 as libc::c_int,
);
@@ -1225,12 +1250,12 @@ unsafe fn build_body_file(
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as libc::size_t,
0 as size_t,
mailmime_parameter_new(
strdup(
b"filename*\x00" as *const u8 as *const libc::c_char,
),
dc_encode_ext_header(&filename_to_send).strdup(),
dc_encode_ext_header(as_str(filename_to_send)).strdup(),
),
);
if !parm.is_null() {
@@ -1252,7 +1277,7 @@ unsafe fn build_body_file(
}
}
content = mailmime_content_new_with_str(mimetype);
filename_encoded = dc_encode_header_words(&filename_to_send);
filename_encoded = dc_encode_header_words(filename_to_send);
clist_insert_after(
(*content).ct_parameters,
(*(*content).ct_parameters).last,
@@ -1262,17 +1287,15 @@ unsafe fn build_body_file(
) as *mut libc::c_void,
);
mime_sub = mailmime_new_empty(content, mime_fields);
let abs_path = dc_get_abs_path(context, path_filename)
.to_c_string()
.unwrap();
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
mailmime_set_body_file(mime_sub, dc_get_abs_path(msg.context, path_filename));
if !ret_file_name_as_sent.is_null() {
*ret_file_name_as_sent = filename_to_send.strdup();
*ret_file_name_as_sent = dc_strdup(filename_to_send)
}
}
}
free(mimetype as *mut libc::c_void);
free(filename_to_send as *mut libc::c_void);
free(filename_encoded as *mut libc::c_void);
mime_sub
@@ -1281,10 +1304,10 @@ unsafe fn build_body_file(
/*******************************************************************************
* Render
******************************************************************************/
unsafe fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
unsafe fn is_file_size_okay(msg: &Message) -> bool {
let mut file_size_okay = true;
let path = msg.param.get(Param::File).unwrap_or_default();
let bytes = dc_get_filebytes(context, &path);
let bytes = dc_get_filebytes(msg.context, &path);
if bytes > (49 * 1024 * 1024 / 4 * 3) {
file_size_okay = false;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -9,31 +9,15 @@ use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/**
* Encode non-ascii-strings as `=?UTF-8?Q?Bj=c3=b6rn_Petersen?=`.
* Belongs to RFC 2047: https://tools.ietf.org/html/rfc2047
*
* We do not fold at position 72; this would result in empty words as `=?utf-8?Q??=` which are correct,
* but cannot be displayed by some mail programs (eg. Android Stock Mail).
* however, this is not needed, as long as _one_ word is not longer than 72 characters.
* _if_ it is, the display may get weird. This affects the subject only.
* the best solution wor all this would be if libetpan encodes the line as only libetpan knowns when a header line is full.
*
* @param to_encode Null-terminated UTF-8-string to encode.
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc::c_char {
let to_encode =
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
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 ret_str: *mut libc::c_char = ptr::null_mut();
let mut cur: *const libc::c_char = to_encode.as_ptr();
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 mmapstr.is_null() {
if to_encode.is_null() || mmapstr.is_null() {
ok_to_continue = false;
}
loop {
@@ -68,7 +52,7 @@ pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc:
b"utf-8\x00" as *const u8 as *const libc::c_char,
mmapstr,
begin,
end.wrapping_offset_from(begin) as libc::size_t,
end.wrapping_offset_from(begin) as size_t,
) {
ok_to_continue = false;
continue;
@@ -84,7 +68,7 @@ pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc:
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as libc::size_t,
cur.wrapping_offset_from(end) as size_t,
)
.is_null()
{
@@ -95,7 +79,7 @@ pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc:
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as libc::size_t,
cur.wrapping_offset_from(begin) as size_t,
)
.is_null()
{
@@ -124,10 +108,10 @@ unsafe fn quote_word(
display_charset: *const libc::c_char,
mmapstr: *mut MMAPString,
word: *const libc::c_char,
size: libc::size_t,
size: size_t,
) -> bool {
let mut cur: *const libc::c_char;
let mut i = 0;
let mut i: size_t = 0i32 as size_t;
let mut hex: [libc::c_char; 4] = [0; 4];
// let mut col: libc::c_int = 0i32;
if mmap_string_append(mmapstr, b"=?\x00" as *const u8 as *const libc::c_char).is_null() {
@@ -190,7 +174,7 @@ unsafe fn get_word(
{
cur = cur.offset(1isize)
}
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as size_t);
*pend = cur;
}
@@ -199,9 +183,9 @@ unsafe fn get_word(
******************************************************************************/
/* see comment below */
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
let mut cur: *const libc::c_char = word;
let mut i = 0;
let mut i: size_t = 0i32 as size_t;
while i < size {
match *cur as libc::c_int {
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
@@ -224,7 +208,7 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
return ptr::null_mut();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token = 0;
let mut cur_token: size_t = 0i32 as size_t;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
@@ -240,31 +224,6 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
let mut cur_token = 0;
let input_c = CString::yolo(input);
unsafe {
let r = mailmime_encoded_phrase_parse(
FROM_ENCODING.as_ptr().cast(),
input_c.as_ptr(),
input.len(),
&mut cur_token,
TO_ENCODING.as_ptr().cast(),
&mut out,
);
if r as u32 != MAILIMF_NO_ERROR || out.is_null() {
input.to_string()
} else {
let res = to_string(out);
free(out.cast());
res
}
}
}
pub fn dc_needs_ext_header(to_check: impl AsRef<str>) -> bool {
let to_check = to_check.as_ref();
@@ -358,13 +317,12 @@ mod tests {
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
buf1 = dc_encode_header_words("abcdef");
buf1 = dc_encode_header_words(b"abcdef\x00" as *const u8 as *const libc::c_char);
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "abcdef");
free(buf1 as *mut libc::c_void);
buf1 = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strncmp(buf1, b"=?utf-8\x00" as *const u8 as *const libc::c_char, 7),

View File

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

View File

@@ -30,9 +30,10 @@ use crate::keyring::*;
use crate::peerstate::*;
use crate::pgp::*;
use crate::securejoin::handle_degrade_event;
use crate::types::*;
use crate::x::*;
#[derive(Debug, Default)]
#[derive(Default)]
pub struct E2eeHelper {
pub encryption_successfull: bool,
cdata_to_free: Option<Box<dyn Any>>,
@@ -86,11 +87,11 @@ impl E2eeHelper {
EncryptPreference::NoPreference
};
let addr = context.get_config(Config::ConfiguredAddr);
let addr = context.sql.get_config(context, "configured_addr");
if let Some(addr) = addr {
let pubkey_ret = load_or_generate_self_public_key(context, &addr).map_err(|err| {
error!(context, "Failed to load public key: {}", err);
error!(context, 0, "Failed to load public key: {}", err);
err
});
if let Ok(public_key) = pubkey_ret {
@@ -112,7 +113,7 @@ impl E2eeHelper {
let peerstate = peerstate.unwrap();
info!(
context,
"dc_e2ee_encrypt {} has peerstate", recipient_addr
0, "dc_e2ee_encrypt {} has peerstate", recipient_addr
);
if let Some(key) = peerstate.peek_key(min_verified as usize) {
keyring.add_owned(key.clone());
@@ -121,6 +122,7 @@ impl E2eeHelper {
} else {
info!(
context,
0,
"dc_e2ee_encrypt {} HAS NO peerstate {}",
recipient_addr,
peerstate.is_some()
@@ -165,7 +167,7 @@ impl E2eeHelper {
let message_to_encrypt: *mut mailmime = mailmime_new(
MAILMIME_MESSAGE as libc::c_int,
ptr::null(),
0 as libc::size_t,
0i32 as size_t,
mailmime_fields_new_empty(),
mailmime_get_content_message(),
ptr::null_mut(),
@@ -296,11 +298,9 @@ impl E2eeHelper {
if (*plain).str_0.is_null() || (*plain).len <= 0 {
ok_to_continue = false;
} else {
if let Ok(ctext_v) = dc_pgp_pk_encrypt(
std::slice::from_raw_parts(
(*plain).str_0 as *const u8,
(*plain).len,
),
if let Some(ctext_v) = dc_pgp_pk_encrypt(
(*plain).str_0 as *const libc::c_void,
(*plain).len,
&keyring,
sign_key.as_ref(),
) {
@@ -311,7 +311,7 @@ impl E2eeHelper {
/* create MIME-structure that will contain the encrypted text */
let mut encrypted_part: *mut mailmime = new_data_part(
ptr::null_mut(),
0 as libc::size_t,
0i32 as size_t,
b"multipart/encrypted\x00" as *const u8
as *const libc::c_char
as *mut libc::c_char,
@@ -386,17 +386,16 @@ impl E2eeHelper {
/*just a pointer into mailmime structure, must not be freed*/
let imffields: *mut mailimf_fields = mailmime_find_mailimf_fields(in_out_message);
let mut message_time = 0;
let mut from = None;
let mut from: *mut libc::c_char = ptr::null_mut();
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers: *mut mailimf_fields = ptr::null_mut();
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
let mut field: *mut mailimf_field =
mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && !(*field).fld_data.fld_from.is_null() {
from = mailimf_find_first_addr((*(*field).fld_data.fld_from).frm_mb_list)
}
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
if !field.is_null() && !(*field).fld_data.fld_orig_date.is_null() {
let orig_date: *mut mailimf_orig_date = (*field).fld_data.fld_orig_date;
@@ -408,32 +407,28 @@ impl E2eeHelper {
}
}
let mut peerstate = None;
let autocryptheader = from
.as_ref()
.and_then(|from| Aheader::from_imffields(from, imffields));
if message_time > 0 {
if let Some(ref from) = from {
peerstate = Peerstate::from_addr(context, &context.sql, from);
let autocryptheader = Aheader::from_imffields(from, imffields);
if message_time > 0 && !from.is_null() {
peerstate = Peerstate::from_addr(context, &context.sql, as_str(from));
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false);
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false);
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
assert!(p.save_to_db(&context.sql, true));
peerstate = Some(p);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false);
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false);
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
assert!(p.save_to_db(&context.sql, true));
peerstate = Some(p);
}
}
/* load private key for decryption */
let self_addr = context.get_config(Config::ConfiguredAddr);
let self_addr = context.sql.get_config(context, "configured_addr");
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(
context,
@@ -441,8 +436,7 @@ impl E2eeHelper {
&context.sql,
) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
peerstate = Peerstate::from_addr(&context, &context.sql, as_str(from));
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
@@ -491,12 +485,14 @@ impl E2eeHelper {
if !gossip_headers.is_null() {
mailimf_fields_free(gossip_headers);
}
free(from as *mut libc::c_void);
}
}
unsafe fn new_data_part(
data: *mut libc::c_void,
data_bytes: libc::size_t,
data_bytes: size_t,
default_content_type: *mut libc::c_char,
default_encoding: libc::c_int,
) -> *mut mailmime {
@@ -610,7 +606,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
let start = std::time::Instant::now();
info!(
context,
"Generating keypair with {} bits, e={} ...", 2048, 65537,
0, "Generating keypair with {} bits, e={} ...", 2048, 65537,
);
match dc_pgp_create_keypair(&self_addr) {
Some((public_key, private_key)) => {
@@ -625,6 +621,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
true => {
info!(
context,
0,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
@@ -692,6 +689,7 @@ unsafe fn update_gossip_peerstates(
} else {
info!(
context,
0,
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
&header.addr,
);
@@ -747,7 +745,7 @@ unsafe fn decrypt_recursive(
&mut decrypted_mime,
) {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
let mut dummy: libc::size_t = 0i32 as libc::size_t;
let mut dummy: size_t = 0i32 as size_t;
let mut test: *mut mailimf_fields = ptr::null_mut();
if mailimf_envelope_and_optional_fields_parse(
(*decrypted_mime).mm_mime_start,
@@ -834,7 +832,7 @@ unsafe fn decrypt_part(
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
/* must not be free()'d */
let mut decoded_data: *const libc::c_char = ptr::null_mut();
let mut decoded_data_bytes: libc::size_t = 0;
let mut decoded_data_bytes: size_t = 0i32 as size_t;
let mut sth_decrypted = false;
*ret_decrypted_mime = ptr::null_mut();
@@ -880,7 +878,7 @@ unsafe fn decrypt_part(
}
} else {
let r: libc::c_int;
let mut current_index: libc::size_t = 0;
let mut current_index: size_t = 0i32 as size_t;
r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
@@ -908,8 +906,9 @@ unsafe fn decrypt_part(
};
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
if let Ok(plain) = dc_pgp_pk_decrypt(
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes),
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,
@@ -917,7 +916,7 @@ unsafe fn decrypt_part(
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index: libc::size_t = 0;
let mut index: size_t = 0i32 as size_t;
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
if mailmime_parse(
plain_buf as *const _,
@@ -1095,12 +1094,15 @@ Sent with my Delta Chat Messenger: https://delta.chat";
let mut decoded_data_bytes = 0;
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
assert!(mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
));
assert_eq!(
mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
),
1
);
println!(
"{:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(

View File

@@ -22,10 +22,6 @@ pub enum Error {
Image(image_meta::ImageError),
#[fail(display = "{:?}", _0)]
Utf8(std::str::Utf8Error),
#[fail(display = "{:?}", _0)]
CStringError(crate::dc_tools::CStringError),
#[fail(display = "PGP: {:?}", _0)]
Pgp(pgp::errors::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -66,18 +62,6 @@ impl From<image_meta::ImageError> for Error {
}
}
impl From<crate::dc_tools::CStringError> for Error {
fn from(err: crate::dc_tools::CStringError) -> Error {
Error::CStringError(err)
}
}
impl From<pgp::errors::Error> for Error {
fn from(err: pgp::errors::Error) -> Error {
Error::Pgp(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -1,229 +0,0 @@
use std::path::PathBuf;
use strum::EnumProperty;
use crate::stock::StockMessage;
impl Event {
/// Returns the corresponding Event id.
pub fn as_id(&self) -> i32 {
self.get_str("id")
.expect("missing id")
.parse()
.expect("invalid id")
}
}
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
pub enum Event {
/// The library-user may write an informational string to the log.
/// Passed to the callback given to dc_context_new().
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @return 0
#[strum(props(id = "100"))]
Info(String),
/// Emitted when SMTP connection is established and login was successful.
///
/// @return 0
#[strum(props(id = "101"))]
SmtpConnected(String),
/// Emitted when IMAP connection is established and login was successful.
///
/// @return 0
#[strum(props(id = "102"))]
ImapConnected(String),
/// Emitted when a message was successfully sent to the SMTP server.
///
/// @return 0
#[strum(props(id = "103"))]
SmtpMessageSent(String),
/// The library-user should write a warning string to the log.
/// Passed to the callback given to dc_context_new().
///
/// This event should not be reported to the end-user using a popup or something like that.
///
/// @return 0
#[strum(props(id = "300"))]
Warning(String),
/// The library-user should report an error to the end-user.
/// Passed to the callback given to dc_context_new().
///
/// As most things are asynchronous, things may go wrong at any time and the user
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
///
/// However, for ongoing processes (eg. configure())
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the _last_ error
/// in a messasge box then.
///
/// @return
#[strum(props(id = "400"))]
Error(String),
/// An action cannot be performed because there is no network available.
///
/// The library will typically try over after a some time
/// and when dc_maybe_network() is called.
///
/// Network errors should be reported to users in a non-disturbing way,
/// however, as network errors may come in a sequence,
/// it is not useful to raise each an every error to the user.
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
///
/// Moreover, if the UI detects that the device is offline,
/// it is probably more useful to report this to the user
/// instead of the string from data2.
///
/// @return 0
#[strum(props(id = "401"))]
ErrorNetwork(String),
/// An action cannot be performed because the user is not in the group.
/// Reported eg. after a call to
/// dc_set_chat_name(), dc_set_chat_profile_image(),
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
/// dc_send_text_msg() or another sending function.
///
/// @return 0
#[strum(props(id = "410"))]
ErrorSelfNotInGroup(String),
/// Messages or chats changed. One or more messages or chats changed for various
/// reasons in the database:
/// - Messages sent, received or removed
/// - Chats created, deleted or archived
/// - A draft has been set
///
/// @return 0
#[strum(props(id = "2000"))]
MsgsChanged { chat_id: u32, msg_id: u32 },
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
///
/// @return 0
#[strum(props(id = "2005"))]
IncomingMsg { chat_id: u32, msg_id: u32 },
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2010"))]
MsgDelivered { chat_id: u32, msg_id: u32 },
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2012"))]
MsgFailed { chat_id: u32, msg_id: u32 },
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
///
/// @return 0
#[strum(props(id = "2015"))]
MsgRead { chat_id: u32, msg_id: u32 },
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
/// and dc_remove_contact_from_chat().
///
/// @return 0
#[strum(props(id = "2020"))]
ChatModified(u32),
/// Contact(s) created, renamed, blocked or deleted.
///
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
/// @return 0
#[strum(props(id = "2030"))]
ContactsChanged(Option<u32>),
/// Location of one or more contact has changed.
///
/// @param data1 (u32) contact_id of the contact for which the location has changed.
/// If the locations of several contacts have been changed,
/// eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
/// @return 0
#[strum(props(id = "2035"))]
LocationChanged(Option<u32>),
/// Inform about the configuration progress started by configure().
///
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
/// @return 0
#[strum(props(id = "2041"))]
ConfigureProgress(usize),
/// Inform about the import/export progress started by dc_imex().
///
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
/// @return 0
#[strum(props(id = "2051"))]
ImexProgress(usize),
/// A file has been exported. A file has been written by dc_imex().
/// This event may be sent multiple times by a single call to dc_imex().
///
/// A typical purpose for a handler of this event may be to make the file public to some system
/// services.
///
/// @param data2 0
/// @return 0
#[strum(props(id = "2052"))]
ImexFileWritten(PathBuf),
/// Progress information of a secure-join handshake from the view of the inviter
/// (Alice, the person who shows the QR code).
///
/// These events are typically sent after a joiner has scanned the QR code
/// generated by dc_get_securejoin_qr().
///
/// @param data1 (int) ID of the contact that wants to join.
/// @param data2 (int) Progress as:
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
/// @return 0
#[strum(props(id = "2060"))]
SecurejoinInviterProgress { contact_id: u32, progress: usize },
/// Progress information of a secure-join handshake from the view of the joiner
/// (Bob, the person who scans the QR code).
/// The events are typically sent while dc_join_securejoin(), which
/// may take some time, is executed.
/// @param data1 (int) ID of the inviting contact.
/// @param data2 (int) Progress as:
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
/// @return 0
#[strum(props(id = "2061"))]
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
// the following events are functions that should be provided by the frontends
/// Requeste a localized string from the frontend.
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
/// the ui may use this value to return different strings on different plural forms.
/// @return (const char*) Null-terminated UTF-8 string.
/// The string will be free()'d by the core,
/// so it must be allocated using malloc() or a compatible function.
/// Return 0 if the ui cannot provide the requested string
/// the core will use a default string in english language then.
#[strum(props(id = "2091"))]
GetString { id: StockMessage, count: usize },
}

View File

@@ -1,6 +1,5 @@
use std::ffi::CString;
use std::net;
use std::ptr;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex, RwLock,
@@ -9,17 +8,13 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::dc_tools::CStringExt;
use crate::dc_tools::*;
use crate::events::Event;
use crate::job::{job_add, Action};
use crate::login_param::LoginParam;
use crate::message::{dc_rfc724_mid_exists, dc_update_msg_move_state, dc_update_server_uid};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::types::*;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
const DC_SUCCESS: usize = 3;
const DC_ALREADY_DONE: usize = 2;
@@ -30,11 +25,15 @@ const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &str = "(FLAGS)";
#[derive(Debug)]
pub struct Imap {
config: Arc<RwLock<ImapConfig>>,
watch: Arc<(Mutex<bool>, Condvar)>,
get_config: dc_get_config_t,
set_config: dc_set_config_t,
precheck_imf: dc_precheck_imf_t,
receive_imf: dc_receive_imf_t,
session: Arc<Mutex<Option<Session>>>,
stream: Arc<RwLock<Option<net::TcpStream>>>,
connected: Arc<Mutex<bool>>,
@@ -42,7 +41,6 @@ pub struct Imap {
should_reconnect: AtomicBool,
}
#[derive(Debug)]
struct OAuth2 {
user: String,
access_token: String,
@@ -67,7 +65,6 @@ enum FolderMeaning {
Other,
}
#[derive(Debug)]
enum Client {
Secure(
imap::Client<native_tls::TlsStream<net::TcpStream>>,
@@ -76,13 +73,11 @@ enum Client {
Insecure(imap::Client<net::TcpStream>, net::TcpStream),
}
#[derive(Debug)]
enum Session {
Secure(imap::Session<native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::Session<net::TcpStream>),
}
#[derive(Debug)]
enum IdleHandle<'a> {
Secure(imap::extensions::idle::Handle<'a, native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::extensions::idle::Handle<'a, net::TcpStream>),
@@ -312,7 +307,6 @@ impl Session {
}
}
#[derive(Debug)]
struct ImapConfig {
pub addr: String,
pub imap_server: String,
@@ -352,12 +346,21 @@ impl Default for ImapConfig {
}
impl Imap {
pub fn new() -> Self {
pub fn new(
get_config: dc_get_config_t,
set_config: dc_set_config_t,
precheck_imf: dc_precheck_imf_t,
receive_imf: dc_receive_imf_t,
) -> Self {
Imap {
session: Arc::new(Mutex::new(None)),
stream: Arc::new(RwLock::new(None)),
config: Arc::new(RwLock::new(ImapConfig::default())),
watch: Arc::new((Mutex::new(false), Condvar::new())),
get_config,
set_config,
precheck_imf,
receive_imf,
connected: Arc::new(Mutex::new(false)),
should_reconnect: AtomicBool::new(false),
}
@@ -417,7 +420,9 @@ impl Imap {
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
let addr: &str = config.addr.as_ref();
if let Some(token) = dc_get_oauth2_access_token(context, addr, imap_pw, true) {
if let Some(token) =
dc_get_oauth2_access_token(context, addr, imap_pw, DC_REGENERATE as usize)
{
let auth = OAuth2 {
user: imap_user.into(),
access_token: token,
@@ -435,12 +440,14 @@ impl Imap {
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
emit_event!(
log_event!(
context,
Event::ErrorNetwork(format!(
"Could not connect to IMAP-server {}:{}. ({})",
imap_server, imap_port, err
))
Event::ERROR_NETWORK,
0,
"Could not connect to IMAP-server {}:{}. ({})",
imap_server,
imap_port,
err
);
return false;
@@ -456,10 +463,7 @@ impl Imap {
true
}
Err((err, _)) => {
emit_event!(
context,
Event::ErrorNetwork(format!("Cannot login ({})", err))
);
log_event!(context, Event::ERROR_NETWORK, 0, "Cannot login ({})", err);
self.unsetup_handle(context);
false
@@ -468,9 +472,12 @@ impl Imap {
}
fn unsetup_handle(&self, context: &Context) {
info!(context, "IMAP unsetup_handle starts");
info!(context, 0, "IMAP unsetup_handle starts");
info!(context, "IMAP unsetup_handle step 1 (closing down stream).");
info!(
context,
0, "IMAP unsetup_handle step 1 (closing down stream)."
);
let stream = self.stream.write().unwrap().take();
if let Some(stream) = stream {
if let Err(err) = stream.shutdown(net::Shutdown::Both) {
@@ -480,7 +487,7 @@ impl Imap {
info!(
context,
"IMAP unsetup_handle step 2 (acquiring session.lock)"
0, "IMAP unsetup_handle step 2 (acquiring session.lock)"
);
if let Some(mut session) = self.session.lock().unwrap().take() {
if let Err(err) = session.close() {
@@ -488,10 +495,10 @@ impl Imap {
}
}
info!(context, "IMAP unsetup_handle step 3 (clearing config).");
info!(context, 0, "IMAP unsetup_handle step 3 (clearing config).");
self.config.write().unwrap().selected_folder = None;
self.config.write().unwrap().selected_mailbox = None;
info!(context, "IMAP unsetup_handle step 4 (disconnected).",);
info!(context, 0, "IMAP unsetup_handle step 4 (disconnected).",);
}
fn free_connect_params(&self) {
@@ -544,7 +551,7 @@ impl Imap {
Some(ref mut session) => match session.capabilities() {
Ok(caps) => {
if !context.sql.is_open() {
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
(true, false, false)
} else {
let can_idle = caps.has_str("IDLE");
@@ -552,18 +559,19 @@ impl Imap {
let caps_list = caps
.iter()
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
emit_event!(
log_event!(
context,
Event::ImapConnected(format!(
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user, caps_list,
))
Event::IMAP_CONNECTED,
0,
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user,
caps_list,
);
(false, can_idle, has_xlist)
}
}
Err(err) => {
info!(context, "CAPABILITY command error: {}", err);
info!(context, 0, "CAPABILITY command error: {}", err);
(true, false, false)
}
},
@@ -639,7 +647,7 @@ impl Imap {
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
if self.config.read().unwrap().selected_folder_needs_expunge {
if let Some(ref folder) = self.config.read().unwrap().selected_folder {
info!(context, "Expunge messages in \"{}\".", folder);
info!(context, 0, "Expunge messages in \"{}\".", folder);
// A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see
// https://tools.ietf.org/html/rfc3501#section-6.4.2
@@ -669,6 +677,7 @@ impl Imap {
Err(err) => {
info!(
context,
0,
"Cannot select folder: {}; {:?}.",
folder.as_ref(),
err
@@ -689,7 +698,7 @@ impl Imap {
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_config(context, &key) {
if let Some(entry) = (self.get_config)(context, &key) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
@@ -705,6 +714,7 @@ impl Imap {
if !self.is_connected() {
info!(
context,
0,
"Cannot fetch from \"{}\" - not connected.",
folder.as_ref()
);
@@ -715,6 +725,7 @@ impl Imap {
if self.select_folder(context, Some(&folder)) == 0 {
info!(
context,
0,
"Cannot select folder \"{}\" for fetching.",
folder.as_ref()
);
@@ -731,6 +742,7 @@ impl Imap {
if mailbox.uid_validity.is_none() {
error!(
context,
0,
"Cannot get UIDVALIDITY for folder \"{}\".",
folder.as_ref(),
);
@@ -742,7 +754,7 @@ impl Imap {
// first time this folder is selected or UIDVALIDITY has changed, init lastseenuid and save it to config
if mailbox.exists == 0 {
info!(context, "Folder \"{}\" is empty.", folder.as_ref());
info!(context, 0, "Folder \"{}\" is empty.", folder.as_ref());
// set lastseenuid=0 for empty folders.
// id we do not do this here, we'll miss the first message
@@ -761,6 +773,7 @@ impl Imap {
self.should_reconnect.store(true, Ordering::Relaxed);
info!(
context,
0,
"No result returned for folder \"{}\".",
folder.as_ref()
);
@@ -783,6 +796,7 @@ impl Imap {
self.set_config_last_seen_uid(context, &folder, uid_validity, last_seen_uid);
info!(
context,
0,
"lastseenuid initialized to {} for {}@{}",
last_seen_uid,
folder.as_ref(),
@@ -801,7 +815,7 @@ impl Imap {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(err) => {
warn!(context, "failed to fetch uids: {}", err);
warn!(context, 0, "failed to fetch uids: {}", err);
return 0;
}
}
@@ -823,12 +837,13 @@ impl Imap {
if 0 == unsafe {
let message_id_c = CString::yolo(message_id);
precheck_imf(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
} {
// check passed, go fetch the rest
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
info!(
context,
0,
"Read error for message {} from \"{}\", trying over later.",
message_id,
folder.as_ref()
@@ -840,6 +855,7 @@ impl Imap {
// check failed
info!(
context,
0,
"Skipping message {} from \"{}\" by precheck.",
message_id,
folder.as_ref(),
@@ -860,6 +876,7 @@ impl Imap {
if read_errors > 0 {
warn!(
context,
0,
"{} mails read from \"{}\" with {} errors.",
read_cnt,
folder.as_ref(),
@@ -868,6 +885,7 @@ impl Imap {
} else {
info!(
context,
0,
"{} mails read from \"{}\".",
read_cnt,
folder.as_ref()
@@ -887,7 +905,7 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
context.sql.set_config(context, &key, Some(&val)).ok();
(self.set_config)(context, &key, Some(&val));
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -912,6 +930,7 @@ impl Imap {
self.should_reconnect.store(true, Ordering::Relaxed);
warn!(
context,
0,
"Error on fetching message #{} from folder \"{}\"; retry={}; error={}.",
server_uid,
folder.as_ref(),
@@ -928,6 +947,7 @@ impl Imap {
if msgs.is_empty() {
warn!(
context,
0,
"Message #{} does not exist in folder \"{}\".",
server_uid,
folder.as_ref()
@@ -957,7 +977,7 @@ impl Imap {
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
dc_receive_imf(
(self.receive_imf)(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
@@ -981,7 +1001,7 @@ impl Imap {
let watch_folder = self.config.read().unwrap().watch_folder.clone();
if self.select_folder(context, watch_folder.as_ref()) == 0 {
warn!(context, "IMAP-IDLE not setup.",);
warn!(context, 0, "IMAP-IDLE not setup.",);
return self.fake_idle(context);
}
@@ -991,7 +1011,7 @@ impl Imap {
let (sender, receiver) = std::sync::mpsc::channel();
let v = self.watch.clone();
info!(context, "IMAP-IDLE SPAWNING");
info!(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() {
@@ -1026,15 +1046,18 @@ impl Imap {
let handle_res = |res| match res {
Ok(()) => {
info!(context, "IMAP-IDLE has data.");
info!(context, 0, "IMAP-IDLE has data.");
}
Err(err) => match err {
imap::error::Error::ConnectionLost => {
info!(context, "IMAP-IDLE wait cancelled, we will reconnect soon.");
info!(
context,
0, "IMAP-IDLE wait cancelled, we will reconnect soon."
);
self.should_reconnect.store(true, Ordering::Relaxed);
}
_ => {
warn!(context, "IMAP-IDLE returns unknown value: {}", err);
warn!(context, 0, "IMAP-IDLE returns unknown value: {}", err);
}
},
};
@@ -1050,7 +1073,7 @@ impl Imap {
if let Ok(res) = worker.as_ref().unwrap().try_recv() {
handle_res(res);
} else {
info!(context, "IMAP-IDLE interrupted");
info!(context, 0, "IMAP-IDLE interrupted");
}
drop(worker.take());
@@ -1068,7 +1091,7 @@ impl Imap {
let fake_idle_start_time = SystemTime::now();
let mut wait_long = false;
info!(context, "IMAP-fake-IDLEing...");
info!(context, 0, "IMAP-fake-IDLEing...");
let mut do_fake_idle = true;
while do_fake_idle {
@@ -1144,6 +1167,7 @@ impl Imap {
} else if folder.as_ref() == dest_folder.as_ref() {
info!(
context,
0,
"Skip moving message; message {}/{} is already in {}...",
folder.as_ref(),
uid,
@@ -1154,6 +1178,7 @@ impl Imap {
} else {
info!(
context,
0,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
@@ -1163,6 +1188,7 @@ impl Imap {
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for moving message.",
folder.as_ref()
);
@@ -1176,6 +1202,7 @@ impl Imap {
Err(err) => {
info!(
context,
0,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
@@ -1196,7 +1223,7 @@ impl Imap {
Ok(_) => true,
Err(err) => {
eprintln!("error copy: {:?}", err);
info!(context, "Cannot copy message.",);
info!(context, 0, "Cannot copy message.",);
false
}
@@ -1207,7 +1234,7 @@ impl Imap {
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".",);
warn!(context, 0, "Cannot mark message as \"Deleted\".",);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
res = DC_SUCCESS;
@@ -1244,7 +1271,7 @@ impl Imap {
Err(err) => {
warn!(
context,
"IMAP failed to store: ({}, {}) {:?}", set, query, err
0, "IMAP failed to store: ({}, {}) {:?}", set, query, err
);
}
}
@@ -1267,6 +1294,7 @@ impl Imap {
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
@@ -1275,11 +1303,12 @@ impl Imap {
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
warn!(context, "Cannot mark message as seen.",);
warn!(context, 0, "Cannot mark message as seen.",);
} else {
res = DC_SUCCESS
}
@@ -1306,6 +1335,7 @@ impl Imap {
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
@@ -1314,6 +1344,7 @@ impl Imap {
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
);
@@ -1380,16 +1411,16 @@ impl Imap {
};
if res == DC_SUCCESS {
info!(context, "$MDNSent just set and MDN will be sent.");
info!(context, 0, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, "$MDNSent already set and MDN already sent.");
info!(context, 0, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = DC_SUCCESS;
info!(
context,
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
0, "Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
}
}
@@ -1420,6 +1451,7 @@ impl Imap {
} else {
info!(
context,
0,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
@@ -1429,6 +1461,7 @@ impl Imap {
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
0,
"Cannot select folder {} for deleting message.",
folder.as_ref()
);
@@ -1449,6 +1482,7 @@ impl Imap {
{
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
@@ -1462,6 +1496,7 @@ impl Imap {
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} not found.",
folder.as_ref(),
server_uid,
@@ -1473,7 +1508,7 @@ impl Imap {
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".");
warn!(context, 0, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
success = true
@@ -1493,7 +1528,7 @@ impl Imap {
return;
}
info!(context, "Configuring IMAP-folders.");
info!(context, 0, "Configuring IMAP-folders.");
let folders = self.list_folders(context).unwrap();
let delimiter = self.config.read().unwrap().imap_delimiter;
@@ -1512,19 +1547,21 @@ impl Imap {
});
if mvbox_folder.is_none() && 0 != (flags as usize & DC_CREATE_MVBOX) {
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
info!(context, 0, "Creating MVBOX-folder \"DeltaChat\"...",);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.create("DeltaChat") {
Ok(_) => {
mvbox_folder = Some("DeltaChat".into());
info!(context, "MVBOX-folder created.",);
info!(context, 0, "MVBOX-folder created.",);
}
Err(err) => {
warn!(
context,
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})", err
0,
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})",
err
);
match session.create(&fallback_folder) {
@@ -1532,11 +1569,11 @@ impl Imap {
mvbox_folder = Some(fallback_folder);
info!(
context,
"MVBOX-folder created as INBOX subfolder. ({})", err
0, "MVBOX-folder created as INBOX subfolder. ({})", err
);
}
Err(err) => {
warn!(context, "Cannot create MVBOX-folder. ({})", err);
warn!(context, 0, "Cannot create MVBOX-folder. ({})", err);
}
}
}
@@ -1582,13 +1619,13 @@ impl Imap {
match session.list(Some(""), Some("*")) {
Ok(list) => {
if list.is_empty() {
warn!(context, "Folder list is empty.",);
warn!(context, 0, "Folder list is empty.",);
}
Some(list)
}
Err(err) => {
eprintln!("list error: {:?}", err);
warn!(context, "Cannot get folder list.",);
warn!(context, 0, "Cannot get folder list.",);
None
}
@@ -1642,53 +1679,3 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
_ => res,
}
}
unsafe fn precheck_imf(
context: &Context,
rfc724_mid: *const libc::c_char,
server_folder: &str,
server_uid: u32,
) -> libc::c_int {
let mut rfc724_mid_exists: libc::c_int = 0i32;
let msg_id: u32;
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
let mut old_server_uid: u32 = 0i32 as u32;
let mut mark_seen: libc::c_int = 0i32;
msg_id = dc_rfc724_mid_exists(
context,
rfc724_mid,
&mut old_server_folder,
&mut old_server_uid,
);
if msg_id != 0i32 as libc::c_uint {
rfc724_mid_exists = 1i32;
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
&& old_server_uid == 0i32 as libc::c_uint
{
info!(context, "[move] detected bbc-self {}", as_str(rfc724_mid),);
mark_seen = 1i32
} else if as_str(old_server_folder) != server_folder {
info!(
context,
"[move] detected moved message {}",
as_str(rfc724_mid),
);
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
context.do_heuristics_moves(server_folder, msg_id);
if 0 != mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
msg_id as libc::c_int,
Params::new(),
0,
);
}
}
libc::free(old_server_folder as *mut libc::c_void);
rfc724_mid_exists
}

View File

@@ -3,7 +3,6 @@ use std::ptr;
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
use mmime::clist::*;
use rand::{thread_rng, Rng};
use crate::chat;
@@ -13,13 +12,13 @@ use crate::context::Context;
use crate::dc_imex::*;
use crate::dc_mimefactory::*;
use crate::dc_tools::*;
use crate::events::Event;
use crate::imap::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::types::*;
use crate::x::*;
/// Thread IDs
@@ -157,10 +156,12 @@ impl Job {
/* if there is a msg-id and it does not exist in the db, cancel sending.
this happends if dc_delete_msgs() was called
before the generated mime was sent out */
if 0 != self.foreign_id && !dc_msg_exists(context, self.foreign_id) {
if 0 != self.foreign_id
&& 0 == unsafe { dc_msg_exists(context, self.foreign_id) }
{
warn!(
context,
"Message {} for job {} does not exist", self.foreign_id, self.job_id,
0, "Message {} for job {} does not exist", self.foreign_id, self.job_id,
);
return;
};
@@ -183,20 +184,22 @@ impl Job {
);
let chat_id: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
0,
)
.unwrap_or_default();
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
context.call_cb(
Event::MSG_DELIVERED,
chat_id as uintptr_t,
self.foreign_id as uintptr_t,
);
}
}
} else {
warn!(context, "Missing recipients for job {}", self.job_id,);
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
}
}
}
@@ -276,7 +279,7 @@ impl Job {
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
info!(
context,
"The message is deleted from the server when all parts are deleted.",
0, "The message is deleted from the server when all parts are deleted.",
);
delete_from_server = 0i32
}
@@ -439,17 +442,18 @@ pub fn perform_imap_fetch(context: &Context) {
.unwrap_or_else(|| 1)
== 0
{
info!(context, "INBOX-watch disabled.",);
info!(context, 0, "INBOX-watch disabled.",);
return;
}
info!(context, "INBOX-fetch started...",);
info!(context, 0, "INBOX-fetch started...",);
inbox.fetch(context);
if inbox.should_reconnect() {
info!(context, "INBOX-fetch aborted, starting over...",);
info!(context, 0, "INBOX-fetch aborted, starting over...",);
inbox.fetch(context);
}
info!(
context,
0,
"INBOX-fetch done in {:.4} ms.",
start.elapsed().as_nanos() as f64 / 1000.0,
);
@@ -463,13 +467,13 @@ pub fn perform_imap_idle(context: &Context) {
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
info!(
context,
"INBOX-IDLE will not be started because of waiting jobs."
0, "INBOX-IDLE will not be started because of waiting jobs."
);
return;
}
info!(context, "INBOX-IDLE started...");
info!(context, 0, "INBOX-IDLE started...");
inbox.idle(context);
info!(context, "INBOX-IDLE ended.");
info!(context, 0, "INBOX-IDLE ended.");
}
pub fn perform_mvbox_fetch(context: &Context) {
@@ -546,16 +550,16 @@ pub fn perform_smtp_jobs(context: &Context) {
state.perform_jobs_needed = 0;
if state.suspended {
info!(context, "SMTP-jobs suspended.",);
info!(context, 0, "SMTP-jobs suspended.",);
return;
}
state.doing_jobs = true;
probe_smtp_network
};
info!(context, "SMTP-jobs started...",);
info!(context, 0, "SMTP-jobs started...",);
job_perform(context, Thread::Smtp, probe_smtp_network);
info!(context, "SMTP-jobs ended.");
info!(context, 0, "SMTP-jobs ended.");
{
let &(ref lock, _) = &*context.smtp_state.clone();
@@ -566,7 +570,7 @@ pub fn perform_smtp_jobs(context: &Context) {
}
pub fn perform_smtp_idle(context: &Context) {
info!(context, "SMTP-idle started...",);
info!(context, 0, "SMTP-idle started...",);
{
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
@@ -574,7 +578,7 @@ pub fn perform_smtp_idle(context: &Context) {
if state.perform_jobs_needed == 1 {
info!(
context,
"SMTP-idle will not be started because of waiting jobs.",
0, "SMTP-idle will not be started because of waiting jobs.",
);
} else {
let dur = get_next_wakeup_time(context, Thread::Smtp);
@@ -592,16 +596,17 @@ pub fn perform_smtp_idle(context: &Context) {
}
}
info!(context, "SMTP-idle ended.",);
info!(context, 0, "SMTP-idle ended.",);
}
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
let t: i64 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
params![thread],
0,
)
.unwrap_or_default();
@@ -642,7 +647,7 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
#[allow(non_snake_case)]
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
let mut success = 0;
/* load message data */
@@ -650,7 +655,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
if mimefactory.is_err() || mimefactory.as_ref().unwrap().from_addr.is_null() {
warn!(
context,
"Cannot load data to send, maybe the message is deleted in between.",
0, "Cannot load data to send, maybe the message is deleted in between.",
);
} else {
let mut mimefactory = mimefactory.unwrap();
@@ -675,12 +680,12 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
mimefactory.msg.param.set_int(Param::Height, height as i32);
}
}
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
dc_msg_save_param_to_disk(&mut mimefactory.msg);
}
}
}
/* create message */
if !dc_mimefactory_render(context, &mut mimefactory) {
if 0 == dc_mimefactory_render(&mut mimefactory) {
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
} else if 0
!= mimefactory
@@ -692,6 +697,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
{
warn!(
context,
0,
"e2e encryption unavailable {} - {:?}",
msg_id,
mimefactory.msg.param.get_int(Param::GuranteeE2ee),
@@ -722,7 +728,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
error!(context, 0, "Failed to set kml sent_timestamp: {:?}", err);
}
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
@@ -730,7 +736,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, "Failed to set msg_location_id: {:?}", err);
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
}
}
}
@@ -743,7 +749,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
== 0
{
mimefactory.msg.param.set_int(Param::GuranteeE2ee, 1);
dc_msg_save_param_to_disk(context, &mut mimefactory.msg);
dc_msg_save_param_to_disk(&mut mimefactory.msg);
}
success = add_smtp_job(context, Action::SendMsgToSmtp, &mut mimefactory);
}
@@ -753,14 +759,14 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
}
pub fn perform_imap_jobs(context: &Context) {
info!(context, "dc_perform_imap_jobs starting.",);
info!(context, 0, "dc_perform_imap_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
*context.probe_imap_network.write().unwrap() = false;
*context.perform_inbox_jobs_needed.write().unwrap() = false;
job_perform(context, Thread::Imap, probe_imap_network);
info!(context, "dc_perform_imap_jobs ended.",);
info!(context, 0, "dc_perform_imap_jobs ended.",);
}
fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
@@ -808,13 +814,14 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
match jobs {
Ok(ref _res) => {}
Err(ref err) => {
info!(context, "query failed: {:?}", err);
info!(context, 0, "query failed: {:?}", err);
}
}
for mut job in jobs.unwrap_or_default() {
info!(
context,
0,
"{}-job #{}, action {} started...",
if thread == Thread::Imap {
"INBOX"
@@ -853,7 +860,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
match job.action {
Action::Unknown => {
warn!(context, "Unknown job id found");
warn!(context, 0, "Unknown job id found");
}
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
Action::DeleteMsgOnImap => job.do_DC_JOB_DELETE_MSG_ON_IMAP(context),
@@ -897,6 +904,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
info!(
context,
0,
"{}-job #{} not yet ready and will be delayed.",
if thread == Thread::Imap {
"INBOX"
@@ -914,6 +922,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
job.update(context);
info!(
context,
0,
"{}-job #{} not succeeded on try #{}, retry in ADD_TIME+{} (in {} seconds).",
if thread == Thread::Imap {
"INBOX"
@@ -988,9 +997,9 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: u32) {
fn send_mdn(context: &Context, msg_id: uint32_t) {
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
if 0 != unsafe { dc_mimefactory_render(&mut mimefactory) } {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
@@ -998,23 +1007,40 @@ fn send_mdn(context: &Context, msg_id: u32) {
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
let pathNfilename: *mut libc::c_char;
let mut success: libc::c_int = 0i32;
let mut recipients: *mut libc::c_char = ptr::null_mut();
let mut param = Params::new();
let path_filename =
dc_get_fine_path_filename(context, "$BLOBDIR", as_str(mimefactory.rfc724_mid));
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
pathNfilename = unsafe {
dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
mimefactory.rfc724_mid,
)
};
if !dc_write_file(context, &path_filename, bytes) {
if pathNfilename.is_null() {
error!(
context,
0,
"Could not find free file name for message with ID <{}>.",
to_string(mimefactory.rfc724_mid),
);
} else if 0
== unsafe {
dc_write_file(
context,
pathNfilename,
(*mimefactory.out).str_0 as *const libc::c_void,
(*mimefactory.out).len,
)
}
{
error!(
context,
0,
"Could not write message <{}> to \"{}\".",
to_string(mimefactory.rfc724_mid),
path_filename.display(),
as_str(pathNfilename),
);
} else {
recipients = unsafe {
@@ -1023,7 +1049,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
b"\x1e\x00" as *const u8 as *const libc::c_char,
)
};
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::File, as_str(pathNfilename));
param.set(Param::Recipients, as_str(recipients));
job_add(
context,
@@ -1042,6 +1068,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
}
unsafe {
free(recipients.cast());
free(pathNfilename.cast());
}
success
}
@@ -1054,7 +1081,7 @@ pub fn job_add(
delay_seconds: i64,
) {
if action == Action::Unknown {
error!(context, "Invalid action passed to job_add");
error!(context, 0, "Invalid action passed to job_add");
return;
}
@@ -1083,7 +1110,7 @@ pub fn job_add(
}
pub fn interrupt_smtp_idle(context: &Context) {
info!(context, "Interrupting SMTP-idle...",);
info!(context, 0, "Interrupting SMTP-idle...",);
let &(ref lock, ref cvar) = &*context.smtp_state.clone();
let mut state = lock.lock().unwrap();
@@ -1094,7 +1121,7 @@ pub fn interrupt_smtp_idle(context: &Context) {
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting IMAP-IDLE...",);
info!(context, 0, "Interrupting IMAP-IDLE...",);
*context.perform_inbox_jobs_needed.write().unwrap() = true;
context.inbox.read().unwrap().interrupt_idle();

View File

@@ -4,7 +4,6 @@ use crate::configure::*;
use crate::context::Context;
use crate::imap::Imap;
#[derive(Debug)]
pub struct JobThread {
pub name: &'static str,
pub folder_config_name: &'static str,
@@ -31,7 +30,7 @@ impl JobThread {
}
pub fn suspend(&self, context: &Context) {
info!(context, "Suspending {}-thread.", self.name,);
info!(context, 0, "Suspending {}-thread.", self.name,);
{
self.state.0.lock().unwrap().suspended = true;
}
@@ -46,7 +45,7 @@ impl JobThread {
}
pub fn unsuspend(&self, context: &Context) {
info!(context, "Unsuspending {}-thread.", self.name);
info!(context, 0, "Unsuspending {}-thread.", self.name);
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
@@ -61,7 +60,7 @@ impl JobThread {
self.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, "Interrupting {}-IDLE...", self.name);
info!(context, 0, "Interrupting {}-IDLE...", self.name);
self.imap.interrupt_idle();
@@ -87,15 +86,16 @@ impl JobThread {
if use_network {
let start = std::time::Instant::now();
if self.connect_to_imap(context) {
info!(context, "{}-fetch started...", self.name);
info!(context, 0, "{}-fetch started...", self.name);
self.imap.fetch(context);
if self.imap.should_reconnect() {
info!(context, "{}-fetch aborted, starting over...", self.name,);
info!(context, 0, "{}-fetch aborted, starting over...", self.name,);
self.imap.fetch(context);
}
info!(
context,
0,
"{}-fetch done in {:.3} ms.",
self.name,
start.elapsed().as_millis(),
@@ -142,6 +142,7 @@ impl JobThread {
if 0 != state.jobs_needed {
info!(
context,
0,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
@@ -171,9 +172,9 @@ impl JobThread {
}
self.connect_to_imap(context);
info!(context, "{}-IDLE started...", self.name,);
info!(context, 0, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
info!(context, 0, "{}-IDLE ended.", self.name);
self.state.0.lock().unwrap().using_handle = false;
}

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::ffi::{CStr, CString};
use std::io::Cursor;
use std::path::Path;
use std::slice;
use libc;
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
@@ -11,6 +12,7 @@ use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::sql::{self, Sql};
use crate::x::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Key {
@@ -104,6 +106,15 @@ impl Key {
}
}
pub fn from_binary(data: *const u8, len: libc::c_int, key_type: KeyType) -> Option<Self> {
if data.is_null() || len == 0 {
return None;
}
let bytes = unsafe { slice::from_raw_parts(data, len as usize) };
Self::from_slice(bytes, key_type)
}
pub fn from_armored_string(
data: &str,
key_type: KeyType,
@@ -141,10 +152,11 @@ impl Key {
) -> Option<Self> {
let addr = self_addr.as_ref();
sql.query_get_value(
sql.query_row_col(
context,
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
&[addr],
0,
)
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
}
@@ -154,10 +166,11 @@ impl Key {
self_addr: impl AsRef<str>,
sql: &Sql,
) -> Option<Self> {
sql.query_get_value(
sql.query_row_col(
context,
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
&[self_addr.as_ref()],
0,
)
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
}
@@ -215,15 +228,30 @@ impl Key {
.expect("failed to serialize key")
}
pub fn write_asc_to_file(&self, file: impl AsRef<Path>, context: &Context) -> bool {
let file_content = self.to_asc(None).into_bytes();
if dc_write_file(context, &file, &file_content) {
return true;
} else {
error!(context, "Cannot write key to {}", file.as_ref().display());
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
if file.is_null() {
return false;
}
let file_content = self.to_asc(None);
let file_content_c = CString::new(file_content).unwrap();
let success = if 0
== unsafe {
dc_write_file(
context,
file,
file_content_c.as_ptr() as *const libc::c_void,
file_content_c.as_bytes().len(),
)
} {
error!(context, 0, "Cannot write key to {}", to_string(file));
false
} else {
true
};
success
}
pub fn fingerprint(&self) -> String {
@@ -233,11 +261,23 @@ impl Key {
}
}
pub fn fingerprint_c(&self) -> *mut libc::c_char {
let res = CString::new(self.fingerprint()).unwrap();
unsafe { strdup(res.as_ptr()) }
}
pub fn formatted_fingerprint(&self) -> String {
let rawhex = self.fingerprint();
dc_format_fingerprint(&rawhex)
}
pub fn formatted_fingerprint_c(&self) -> *mut libc::c_char {
let res = CString::new(self.formatted_fingerprint()).unwrap();
unsafe { strdup(res.as_ptr()) }
}
pub fn split_key(&self) -> Option<Key> {
match self {
Key::Public(_) => None,
@@ -283,6 +323,14 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
res
}
pub fn dc_format_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
let res = dc_format_fingerprint(input);
let res_c = CString::new(res).unwrap();
unsafe { strdup(res_c.as_ptr()) }
}
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
pub fn dc_normalize_fingerprint(fp: &str) -> String {
fp.to_uppercase()
@@ -291,6 +339,14 @@ pub fn dc_normalize_fingerprint(fp: &str) -> String {
.collect()
}
pub fn dc_normalize_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
let res = dc_normalize_fingerprint(input);
let res_c = CString::new(res).unwrap();
unsafe { strdup(res_c.as_ptr()) }
}
#[cfg(test)]
mod tests {
use super::*;
@@ -393,27 +449,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
assert_eq!(private_key, private_key2);
}
#[test]
fn test_from_slice_bad_data() {
let mut bad_data: [u8; 4096] = [0; 4096];
for i in 0..4096 {
bad_data[i] = (i & 0xff) as u8;
}
for j in 0..(4096 / 40) {
let bad_key = Key::from_slice(
&bad_data[j..j + 4096 / 2 + j],
if 0 != j & 1 {
KeyType::Public
} else {
KeyType::Private
},
);
assert!(bad_key.is_none());
}
}
#[test]
#[ignore] // is too expensive
fn test_ascii_roundtrip() {

View File

@@ -33,10 +33,11 @@ impl<'a> Keyring<'a> {
self_addr: impl AsRef<str>,
sql: &Sql,
) -> bool {
sql.query_get_value(
sql.query_row_col(
context,
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
&[self_addr.as_ref()],
0,
)
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
.map(|key| self.add_owned(key))

View File

@@ -1,4 +1,4 @@
#![deny(clippy::correctness, missing_debug_implementations)]
#![deny(clippy::correctness)]
// TODO: make all of these errors, such that clippy actually passes.
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
// This is nice, but for now just annoying.
@@ -18,17 +18,12 @@ extern crate strum;
extern crate strum_macros;
#[macro_use]
extern crate jetscii;
#[macro_use]
extern crate debug_stub_derive;
#[macro_use]
mod log;
#[macro_use]
pub mod error;
pub(crate) mod events;
pub use events::*;
mod aheader;
pub mod chat;
pub mod chatlist;
@@ -54,6 +49,7 @@ pub mod qr;
mod smtp;
pub mod sql;
mod stock;
pub mod types;
pub mod x;
pub mod dc_array;

View File

@@ -3,16 +3,17 @@ use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::chat;
use crate::constants::Event;
use crate::constants::*;
use crate::context::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::job::*;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
// location handling
#[derive(Debug, Clone, Default)]
@@ -83,6 +84,7 @@ impl Kml {
Err(e) => {
error!(
context,
0,
"Location parsing: Error at position {}: {:?}",
reader.buffer_position(),
e
@@ -214,7 +216,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
.is_ok()
{
if 0 != seconds && !is_sending_locations_before {
msg = dc_msg_new(Viewtype::Text);
msg = dc_msg_new(context, Viewtype::Text);
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
@@ -224,7 +226,11 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
}
context.call_cb(Event::ChatModified(chat_id));
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
job_add(
@@ -260,16 +266,16 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
let mut continue_streaming = false;
if let Ok(chats) = context.sql.query_map(
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
params![time()],
|row| row.get::<_, i32>(0),
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
) {
for chat_id in chats {
if let Err(err) = context.sql.execute(
params![time()], |row| row.get::<_, i32>(0),
|chats| {
let mut continue_streaming = false;
for chat in chats {
let chat_id = chat?;
context.sql.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
params![
@@ -280,19 +286,16 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
chat_id,
1,
]
) {
warn!(context, "failed to store location {:?}", err);
} else {
)?;
continue_streaming = true;
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
};
schedule_MAYBE_SEND_LOCATIONS(context, 0);
Ok(continue_streaming as libc::c_int)
}
if continue_streaming {
context.call_cb(Event::LocationChanged(Some(1)));
};
schedule_MAYBE_SEND_LOCATIONS(context, 0);
}
continue_streaming as libc::c_int
).unwrap_or_default()
}
pub fn get_range(
@@ -364,7 +367,7 @@ fn is_marker(txt: &str) -> bool {
pub fn delete_all(context: &Context) -> Result<(), Error> {
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
context.call_cb(Event::LocationChanged(None));
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
Ok(())
}
@@ -543,81 +546,76 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let mut continue_streaming: libc::c_int = 1;
info!(
context,
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
if let Ok(rows) = context.sql.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = 1;
context
.sql
.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \
WHERE locations_send_until>?;",
params![now],
|row| {
let chat_id: i32 = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = 1;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
rows.filter_map(|v| v.transpose())
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
},
) {
let msgs = context
.sql
.prepare(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
|mut stmt_locations, _| {
let msgs = rows
.into_iter()
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
Ok(None)
} else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
}
},
|rows| {
context.sql.prepare(
"SELECT id \
FROM locations \
WHERE from_id=? \
AND timestamp>=? \
AND timestamp>? \
AND independent=0 \
ORDER BY timestamp;",
|mut stmt_locations, _| {
for (chat_id, locations_send_begin, locations_last_sent) in
rows.filter_map(|r| match r {
Ok(Some(v)) => Some(v),
_ => None,
})
{
// TODO: do I need to reset?
if !stmt_locations
.exists(params![1, locations_send_begin, locations_last_sent,])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
None
} else {
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = dc_msg_new(Viewtype::Text);
msg.hidden = true;
msg.param.set_int(Param::Cmd, 9);
Some((chat_id, msg))
continue;
}
})
.collect::<Vec<_>>();
Ok(msgs)
},
)
.unwrap_or_default(); // TODO: Better error handling
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = dc_msg_new(context, Viewtype::Text);
msg.hidden = true;
msg.param.set_int(Param::Cmd, 9);
// TODO: handle cleanup on error
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
}
Ok(())
},
)
},
)
.unwrap(); // TODO: Better error handling
for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
}
}
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
@@ -648,7 +646,11 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
context.call_cb(Event::ChatModified(chat_id));
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as usize,
0,
);
}
}
}

View File

@@ -1,39 +1,66 @@
#[macro_export]
macro_rules! info {
($ctx:expr, $msg:expr) => {
info!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Info(formatted));
($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, $msg:expr) => {
warn!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Warning(formatted));
($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, $msg:expr) => {
error!($ctx, $msg,)
($ctx:expr, $data1:expr, $msg:expr) => {
error!($ctx, $data1, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Error(formatted));
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);
}};
}
#[macro_export]
macro_rules! emit_event {
($ctx:expr, $event:expr) => {
$ctx.call_cb($event);
($ctx:expr, $event:expr, $data1:expr, $data2:expr) => {
$ctx.call_cb($event, $data1 as libc::uintptr_t, $data2 as libc::uintptr_t);
};
}

View File

@@ -1,5 +1,5 @@
use std::ffi::CString;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::ptr;
use deltachat_derive::{FromSql, ToSql};
@@ -9,16 +9,15 @@ use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
use crate::dc_mimeparser::SystemMessage;
use crate::dc_tools::*;
use crate::error::Error;
use crate::events::Event;
use crate::job::*;
use crate::lot::{Lot, LotState, Meaning};
use crate::param::*;
use crate::pgp::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
/// In practice, the user additionally cuts the string himself pixel-accurate.
@@ -88,7 +87,7 @@ impl Lot {
self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into());
self.text1_meaning = Meaning::Text1Draft;
} else if msg.from_id == DC_CONTACT_ID_SELF {
if dc_msg_is_info(msg) || chat.is_self_talk() {
if 0 != dc_msg_is_info(msg) || chat.is_self_talk() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
@@ -96,7 +95,7 @@ impl Lot {
self.text1_meaning = Meaning::Text1Self;
}
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
if dc_msg_is_info(msg) || contact.is_none() {
if 0 != dc_msg_is_info(msg) || contact.is_none() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
@@ -137,8 +136,8 @@ impl Lot {
/// to check if a mail was sent, use dc_msg_is_sent()
/// approx. max. length returned by dc_msg_get_text()
/// approx. max. length returned by dc_get_msg_info()
#[derive(Debug, Clone)]
pub struct Message {
#[derive(Clone)]
pub struct Message<'a> {
pub id: u32,
pub from_id: u32,
pub to_id: u32,
@@ -151,8 +150,9 @@ pub struct Message {
pub timestamp_sent: i64,
pub timestamp_rcvd: i64,
pub text: Option<String>,
pub context: &'a Context,
pub rfc724_mid: *mut libc::c_char,
pub in_reply_to: Option<String>,
pub in_reply_to: *mut libc::c_char,
pub server_folder: Option<String>,
pub server_uid: u32,
// TODO: enum
@@ -174,10 +174,11 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
let msg = msg.unwrap();
let rawtxt: Option<String> = context.sql.query_get_value(
let rawtxt: Option<String> = context.sql.query_row_col(
context,
"SELECT txt_raw FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if rawtxt.is_none() {
@@ -269,10 +270,15 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
_ => {}
}
if let Some(path) = dc_msg_get_file(context, &msg) {
let bytes = dc_get_filebytes(context, &path);
ret += &format!("\nFile: {}, {}, bytes\n", path.display(), bytes);
let p = dc_msg_get_file(&msg);
if !p.is_null() && 0 != *p.offset(0isize) as libc::c_int {
ret += &format!(
"\nFile: {}, {}, bytes\n",
as_str(p),
dc_get_filebytes(context, as_path(p)) as libc::c_int,
);
}
free(p as *mut libc::c_void);
if msg.type_0 != Viewtype::Text {
ret += "Type: ";
@@ -304,11 +310,11 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
ret.strdup()
}
pub fn dc_msg_new_untyped() -> Message {
dc_msg_new(Viewtype::Unknown)
pub unsafe fn dc_msg_new_untyped<'a>(context: &'a Context) -> Message<'a> {
dc_msg_new(context, Viewtype::Unknown)
}
pub fn dc_msg_new(viewtype: Viewtype) -> Message {
pub fn dc_msg_new<'a>(context: &'a Context, viewtype: Viewtype) -> Message<'a> {
Message {
id: 0,
from_id: 0,
@@ -322,8 +328,9 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
timestamp_sent: 0,
timestamp_rcvd: 0,
text: None,
context,
rfc724_mid: std::ptr::null_mut(),
in_reply_to: None,
in_reply_to: std::ptr::null_mut(),
server_folder: None,
server_uid: 0,
is_dc_message: 0,
@@ -334,10 +341,11 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
}
}
impl Drop for Message {
impl<'a> Drop for Message<'a> {
fn drop(&mut self) {
unsafe {
free(self.rfc724_mid.cast());
free(self.in_reply_to.cast());
}
}
}
@@ -372,10 +380,17 @@ pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)>
KNOWN.get(extension).map(|x| *x)
}
pub unsafe fn dc_msg_get_file(context: &Context, msg: &Message) -> Option<PathBuf> {
msg.param
.get(Param::File)
.map(|f| dc_get_abs_path(context, f))
pub unsafe fn dc_msg_get_file(msg: &Message) -> *mut libc::c_char {
let mut file_abs = ptr::null_mut();
if let Some(file_rel) = msg.param.get(Param::File) {
file_abs = dc_get_abs_path(msg.context, file_rel);
}
if !file_abs.is_null() {
file_abs
} else {
dc_strdup(0 as *const libc::c_char)
}
}
/**
@@ -425,7 +440,7 @@ pub fn dc_msg_get_timestamp(msg: &Message) -> i64 {
}
}
pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error> {
pub fn dc_msg_load_from_db<'a>(context: &'a Context, id: u32) -> Result<Message<'a>, Error> {
context.sql.query_row(
"SELECT \
m.id,rfc724_mid,m.mime_in_reply_to,m.server_folder,m.server_uid,m.move_state,m.chat_id, \
@@ -436,10 +451,14 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
params![id as i32],
|row| {
unsafe {
let mut msg = dc_msg_new_untyped();
let mut msg = dc_msg_new_untyped(context);
msg.context = context;
msg.id = row.get::<_, i32>(0)? as u32;
msg.rfc724_mid = row.get::<_, String>(1)?.strdup();
msg.in_reply_to = row.get::<_, Option<String>>(2)?;
msg.in_reply_to = match row.get::<_, Option<String>>(2)? {
Some(s) => s.strdup(),
None => std::ptr::null_mut(),
};
msg.server_folder = row.get::<_, Option<String>>(3)?;
msg.server_uid = row.get(4)?;
msg.move_state = row.get(5)?;
@@ -458,7 +477,7 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
warn!(context, "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", id);
warn!(context, 0, "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", id);
text = String::from_utf8_lossy(buf).into_owned();
}
} else {
@@ -487,10 +506,11 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
}
pub unsafe fn dc_get_mime_headers(context: &Context, msg_id: u32) -> *mut libc::c_char {
let headers: Option<String> = context.sql.query_get_value(
let headers: Option<String> = context.sql.query_row_col(
context,
"SELECT mime_headers FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if let Some(headers) = headers {
@@ -519,10 +539,7 @@ pub unsafe fn dc_delete_msgs(context: &Context, msg_ids: *const u32, msg_cnt: li
}
if 0 != msg_cnt {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
job_kill_action(context, Action::Housekeeping);
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
};
@@ -562,7 +579,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
);
if msgs.is_err() {
warn!(context, "markseen_msgs failed: {:?}", msgs);
warn!(context, 0, "markseen_msgs failed: {:?}", msgs);
return false;
}
let mut send_event = false;
@@ -572,7 +589,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
if curr_blocked == Blocked::Not {
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
dc_update_msg_state(context, id, MessageState::InSeen);
info!(context, "Seen message #{}.", id);
info!(context, 0, "Seen message #{}.", id);
job_add(
context,
@@ -590,10 +607,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
}
if send_event {
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
context.call_cb(Event::MSGS_CHANGED, 0, 0);
}
true
@@ -629,7 +643,7 @@ pub fn dc_star_msgs(
.is_ok()
}
pub fn dc_get_msg(context: &Context, msg_id: u32) -> Result<Message, Error> {
pub fn dc_get_msg<'a>(context: &'a Context, msg_id: u32) -> Result<Message<'a>, Error> {
dc_msg_load_from_db(context, msg_id)
}
@@ -687,10 +701,11 @@ pub unsafe fn dc_msg_get_filename(msg: &Message) -> *mut libc::c_char {
}
}
pub fn dc_msg_get_filebytes(context: &Context, msg: &Message) -> u64 {
pub unsafe fn dc_msg_get_filebytes(msg: &Message) -> uint64_t {
if let Some(file) = msg.param.get(Param::File) {
return dc_get_filebytes(context, &file);
return dc_get_filebytes(msg.context, &file);
}
0
}
@@ -706,18 +721,23 @@ pub fn dc_msg_get_duration(msg: &Message) -> libc::c_int {
msg.param.get_int(Param::Duration).unwrap_or_default()
}
pub fn dc_msg_get_showpadlock(msg: &Message) -> bool {
msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0
// TODO should return bool /rtn
pub fn dc_msg_get_showpadlock(msg: &Message) -> libc::c_int {
if msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0 {
return 1;
}
0
}
pub fn dc_msg_get_summary(context: &Context, msg: &mut Message, chat: Option<&Chat>) -> Lot {
pub unsafe fn dc_msg_get_summary<'a>(msg: &mut Message<'a>, chat: Option<&Chat<'a>>) -> Lot {
let mut ret = Lot::new();
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(context, msg.chat_id) {
if let Ok(chat) = Chat::load_from_db(msg.context, msg.chat_id) {
chat_loaded = chat;
&chat_loaded
} else {
@@ -728,18 +748,17 @@ pub fn dc_msg_get_summary(context: &Context, msg: &mut Message, chat: Option<&Ch
let contact = if msg.from_id != DC_CONTACT_ID_SELF as libc::c_uint
&& ((*chat).typ == Chattype::Group || (*chat).typ == Chattype::VerifiedGroup)
{
Contact::get_by_id(context, msg.from_id).ok()
Contact::get_by_id((*chat).context, msg.from_id).ok()
} else {
None
};
ret.fill(msg, chat, contact.as_ref(), context);
ret.fill(msg, chat, contact.as_ref(), msg.context);
ret
}
pub unsafe fn dc_msg_get_summarytext(
context: &Context,
msg: &mut Message,
approx_characters: usize,
) -> *mut libc::c_char {
@@ -748,7 +767,7 @@ pub unsafe fn dc_msg_get_summarytext(
msg.text.as_ref(),
&mut msg.param,
approx_characters,
context,
msg.context,
)
.strdup()
}
@@ -768,7 +787,7 @@ pub fn dc_msg_get_summarytext_by_raw(
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
Viewtype::Audio | Viewtype::File => {
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
if param.get_int(Param::Cmd) == Some(6) {
append_text = false;
context
.stock_str(StockMessage::AcSetupMsgSubject)
@@ -794,7 +813,7 @@ pub fn dc_msg_get_summarytext_by_raw(
}
}
_ => {
if param.get_cmd() != SystemMessage::LocationOnly {
if param.get_int(Param::Cmd) != Some(9) {
"".to_string()
} else {
append_text = false;
@@ -827,27 +846,48 @@ pub unsafe fn dc_msg_has_deviating_timestamp(msg: &Message) -> libc::c_int {
(sort_timestamp / 86400 != send_timestamp / 86400) as libc::c_int
}
pub fn dc_msg_is_sent(msg: &Message) -> bool {
msg.state as i32 >= MessageState::OutDelivered as i32
// TODO should return bool /rtn
pub fn dc_msg_is_sent(msg: &Message) -> libc::c_int {
if msg.state as i32 >= MessageState::OutDelivered as i32 {
1
} else {
0
}
}
pub fn dc_msg_is_starred(msg: &Message) -> bool {
msg.starred
}
pub fn dc_msg_is_forwarded(msg: &Message) -> bool {
0 != msg.param.get_int(Param::Forwarded).unwrap_or_default()
// TODO should return bool /rtn
pub fn dc_msg_is_forwarded(msg: &Message) -> libc::c_int {
if 0 != msg.param.get_int(Param::Forwarded).unwrap_or_default() {
1
} else {
0
}
}
pub fn dc_msg_is_info(msg: &Message) -> bool {
let cmd = msg.param.get_cmd();
msg.from_id == 2i32 as libc::c_uint
// TODO should return bool /rtn
pub fn dc_msg_is_info(msg: &Message) -> libc::c_int {
let cmd = msg.param.get_int(Param::Cmd).unwrap_or_default();
if msg.from_id == 2i32 as libc::c_uint
|| msg.to_id == 2i32 as libc::c_uint
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
|| 0 != cmd && cmd != 6i32
{
return 1;
}
0
}
pub fn dc_msg_is_increation(msg: &Message) -> bool {
chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing
// TODO should return bool /rtn
pub fn dc_msg_is_increation(msg: &Message) -> libc::c_int {
if chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing {
1
} else {
0
}
}
pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
@@ -855,20 +895,33 @@ pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
return false;
}
msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
msg.param.get_int(Param::Cmd) == Some(6)
}
pub unsafe fn dc_msg_get_setupcodebegin(context: &Context, msg: &Message) -> *mut libc::c_char {
pub unsafe fn dc_msg_get_setupcodebegin(msg: &Message) -> *mut libc::c_char {
let mut filename: *mut libc::c_char = ptr::null_mut();
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut buf_bytes: size_t = 0i32 as size_t;
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_headerline: *const libc::c_char = ptr::null();
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_setupcodebegin: *const libc::c_char = ptr::null();
let mut ret: *mut libc::c_char = ptr::null_mut();
if dc_msg_is_setupmessage(msg) {
if let Some(filename) = dc_msg_get_file(context, msg) {
if let Some(mut buf) = dc_read_file_safe(context, filename) {
filename = dc_msg_get_file(msg);
if !(filename.is_null() || *filename.offset(0isize) as libc::c_int == 0i32) {
if !(0
== dc_read_file(
msg.context,
filename,
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut buf_bytes,
)
|| buf.is_null()
|| buf_bytes <= 0)
{
if dc_split_armored_data(
buf.as_mut_ptr().cast(),
buf,
&mut buf_headerline,
&mut buf_setupcodebegin,
ptr::null_mut(),
@@ -884,6 +937,8 @@ pub unsafe fn dc_msg_get_setupcodebegin(context: &Context, msg: &Message) -> *mu
}
}
}
free(filename as *mut libc::c_void);
free(buf as *mut libc::c_void);
if !ret.is_null() {
ret
} else {
@@ -922,7 +977,6 @@ pub fn dc_msg_set_duration(msg: &mut Message, duration: libc::c_int) {
}
pub fn dc_msg_latefiling_mediasize(
context: &Context,
msg: &mut Message,
width: libc::c_int,
height: libc::c_int,
@@ -935,20 +989,20 @@ pub fn dc_msg_latefiling_mediasize(
if duration > 0 {
msg.param.set_int(Param::Duration, duration);
}
dc_msg_save_param_to_disk(context, msg);
dc_msg_save_param_to_disk(msg);
}
pub fn dc_msg_save_param_to_disk(context: &Context, msg: &mut Message) -> bool {
pub fn dc_msg_save_param_to_disk(msg: &mut Message) -> bool {
sql::execute(
context,
&context.sql,
msg.context,
&msg.context.sql,
"UPDATE msgs SET param=? WHERE id=?;",
params![msg.param.to_string(), msg.id as i32],
)
.is_ok()
}
pub fn dc_msg_new_load(context: &Context, msg_id: u32) -> Result<Message, Error> {
pub fn dc_msg_new_load<'a>(context: &'a Context, msg_id: u32) -> Result<Message<'a>, Error> {
dc_msg_load_from_db(context, msg_id)
}
@@ -978,22 +1032,25 @@ The value is also used for CC:-summaries */
// Context functions to work with messages
pub fn dc_msg_exists(context: &Context, msg_id: u32) -> bool {
if msg_id <= DC_CHAT_ID_LAST_SPECIAL {
return false;
pub unsafe fn dc_msg_exists(context: &Context, msg_id: u32) -> libc::c_int {
if msg_id <= 9 {
return 0;
}
let chat_id: Option<u32> = context.sql.query_get_value(
let chat_id: Option<i32> = context.sql.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
params![msg_id],
params![msg_id as i32],
0,
);
if let Some(chat_id) = chat_id {
chat_id != DC_CHAT_ID_TRASH
} else {
false
if chat_id != 3 {
return 1;
}
}
0
}
pub fn dc_update_msg_move_state(
@@ -1019,7 +1076,7 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
}
if let Some(error) = error {
msg.param.set(Param::Error, error.as_ref());
error!(context, "{}", error.as_ref());
error!(context, 0, "{}", error.as_ref());
}
if sql::execute(
@@ -1030,10 +1087,11 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
)
.is_ok()
{
context.call_cb(Event::MsgFailed {
chat_id: msg.chat_id,
msg_id,
});
context.call_cb(
Event::MSG_FAILED,
msg.chat_id as uintptr_t,
msg_id as uintptr_t,
);
}
}
}
@@ -1104,10 +1162,11 @@ pub unsafe fn dc_mdn_from_ext(
/* send event about new state */
let ist_cnt: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
params![*ret_msg_id as i32],
0,
)
.unwrap_or_default();
/*
@@ -1146,13 +1205,13 @@ pub fn dc_get_real_msg_cnt(context: &Context) -> libc::c_int {
) {
Ok(res) => res,
Err(err) => {
error!(context, "dc_get_real_msg_cnt() failed. {}", err);
error!(context, 0, "dc_get_real_msg_cnt() failed. {}", err);
0
}
}
}
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> size_t {
match context.sql.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
@@ -1160,9 +1219,9 @@ pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
rusqlite::NO_PARAMS,
|row| row.get::<_, isize>(0),
) {
Ok(res) => res as libc::size_t,
Ok(res) => res as size_t,
Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
error!(context, 0, "dc_get_deaddrop_msg_cnt() failed. {}", err);
0
}
}
@@ -1177,7 +1236,7 @@ pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: *const libc::c_char) ->
) {
Ok(res) => res,
Err(err) => {
error!(context, "dc_get_rfc724_mid_cnt() failed. {}", err);
error!(context, 0, "dc_get_rfc724_mid_cnt() failed. {}", err);
0
}
}
@@ -1231,7 +1290,7 @@ pub fn dc_update_server_uid(
) {
Ok(_) => {}
Err(err) => {
warn!(context, "msg: failed to update server_uid: {}", err);
warn!(context, 0, "msg: failed to update server_uid: {}", err);
}
}
}
@@ -1264,7 +1323,7 @@ mod tests {
let chat = chat::create_by_contact_id(ctx, contact).unwrap();
let mut msg = dc_msg_new(Viewtype::Text);
let mut msg = dc_msg_new(ctx, Viewtype::Text);
let msg_id = chat::prepare_msg(ctx, chat, &mut msg).unwrap();

View File

@@ -74,14 +74,14 @@ pub fn dc_get_oauth2_access_token(
context: &Context,
addr: impl AsRef<str>,
code: impl AsRef<str>,
regenerate: bool,
flags: usize,
) -> Option<String> {
if let Some(oauth2) = Oauth2::from_address(addr) {
let lock = context.oauth2_critical.clone();
let _l = lock.lock().unwrap();
// read generated token
if !regenerate && !is_expired(context) {
if 0 == flags & 0x1 && !is_expired(context) {
let access_token = context.sql.get_config(context, "oauth2_access_token");
if access_token.is_some() {
// success
@@ -97,7 +97,10 @@ pub fn dc_get_oauth2_access_token(
let (redirect_uri, token_url, update_redirect_uri_on_success) =
if refresh_token.is_none() || refresh_token_for != code.as_ref() {
info!(context, "Generate OAuth2 refresh_token and access_token...",);
info!(
context,
0, "Generate OAuth2 refresh_token and access_token...",
);
(
context
.sql
@@ -109,7 +112,7 @@ pub fn dc_get_oauth2_access_token(
} else {
info!(
context,
"Regenerate OAuth2 access_token by refresh_token...",
0, "Regenerate OAuth2 access_token by refresh_token...",
);
(
context
@@ -131,7 +134,7 @@ pub fn dc_get_oauth2_access_token(
if response.is_err() {
warn!(
context,
"Error calling OAuth2 at {}: {:?}", token_url, response
0, "Error calling OAuth2 at {}: {:?}", token_url, response
);
return None;
}
@@ -139,6 +142,7 @@ pub fn dc_get_oauth2_access_token(
if !response.status().is_success() {
warn!(
context,
0,
"Error calling OAuth2 at {}: {:?}",
token_url,
response.status()
@@ -150,7 +154,7 @@ pub fn dc_get_oauth2_access_token(
if parsed.is_err() {
warn!(
context,
"Failed to parse OAuth2 JSON response from {}: error: {:?}", token_url, parsed
0, "Failed to parse OAuth2 JSON response from {}: error: {:?}", token_url, parsed
);
return None;
}
@@ -191,12 +195,12 @@ pub fn dc_get_oauth2_access_token(
.ok();
}
} else {
warn!(context, "Failed to find OAuth2 access token");
warn!(context, 0, "Failed to find OAuth2 access token");
}
response.access_token
} else {
warn!(context, "Internal OAuth2 error: 2");
warn!(context, 0, "Internal OAuth2 error: 2");
None
}
@@ -216,13 +220,12 @@ pub fn dc_get_oauth2_addr(
return None;
}
if let Some(access_token) =
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
if let Some(access_token) = dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), 0)
{
let addr_out = oauth2.get_addr(context, access_token);
if addr_out.is_none() {
// regenerate
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true) {
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, 0x1) {
oauth2.get_addr(context, access_token)
} else {
None
@@ -265,12 +268,17 @@ impl Oauth2 {
// }
let response = reqwest::Client::new().get(&userinfo_url).send();
if response.is_err() {
warn!(context, "Error getting userinfo: {:?}", response);
warn!(context, 0, "Error getting userinfo: {:?}", response);
return None;
}
let mut response = response.unwrap();
if !response.status().is_success() {
warn!(context, "Error getting userinfo: {:?}", response.status());
warn!(
context,
0,
"Error getting userinfo: {:?}",
response.status()
);
return None;
}
@@ -278,19 +286,19 @@ impl Oauth2 {
if parsed.is_err() {
warn!(
context,
"Failed to parse userinfo JSON response: {:?}", parsed
0, "Failed to parse userinfo JSON response: {:?}", parsed
);
return None;
}
if let Ok(response) = parsed {
let addr = response.get("email");
if addr.is_none() {
warn!(context, "E-mail missing in userinfo.");
warn!(context, 0, "E-mail missing in userinfo.");
}
addr.map(|addr| addr.to_string())
} else {
warn!(context, "Failed to parse userinfo.");
warn!(context, 0, "Failed to parse userinfo.");
None
}
}

View File

@@ -4,7 +4,6 @@ use std::str;
use num_traits::FromPrimitive;
use crate::dc_mimeparser::SystemMessage;
use crate::error;
/// Available param keys.
@@ -95,7 +94,7 @@ impl fmt::Display for Params {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, (key, value)) in self.inner.iter().enumerate() {
if i > 0 {
writeln!(f)?;
write!(f, "\n")?;
}
write!(f, "{}={}", *key as u8 as char, value)?;
}
@@ -179,13 +178,6 @@ impl Params {
self.get(key).and_then(|s| s.parse().ok())
}
/// Get the parameter behind `Param::Cmd` interpreted as `SystemMessage`.
pub fn get_cmd(&self) -> SystemMessage {
self.get_int(Param::Cmd)
.and_then(SystemMessage::from_i32)
.unwrap_or_default()
}
/// Get the given parameter and parse as `f64`.
pub fn get_float(&self, key: Param) -> Option<f64> {
self.get(key).and_then(|s| s.parse().ok())

View File

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

View File

@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::CStr;
use std::io::Cursor;
use std::ptr;
@@ -12,9 +13,9 @@ use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
use rand::thread_rng;
use crate::dc_tools::*;
use crate::error::Error;
use crate::key::*;
use crate::keyring::*;
use crate::types::*;
use crate::x::*;
pub unsafe fn dc_split_armored_data(
@@ -25,7 +26,7 @@ pub unsafe fn dc_split_armored_data(
ret_base64: *mut *const libc::c_char,
) -> bool {
let mut success = false;
let mut line_chars: libc::size_t = 0;
let mut line_chars: size_t = 0i32 as size_t;
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
let mut p2: *mut libc::c_char;
@@ -105,7 +106,7 @@ pub unsafe fn dc_split_armored_data(
}
p1 = p1.offset(1isize);
line = p1;
line_chars = 0;
line_chars = 0i32 as size_t
} else {
p1 = p1.offset(1isize);
line_chars = line_chars.wrapping_add(1)
@@ -187,11 +188,15 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
}
pub fn dc_pgp_pk_encrypt(
plain: &[u8],
plain_text: *const libc::c_void,
plain_bytes: size_t,
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Result<String, Error> {
let lit_msg = Message::new_literal_bytes("", plain);
) -> Option<String> {
assert!(!plain_text.is_null() && !plain_bytes > 0, "invalid input");
let bytes = unsafe { std::slice::from_raw_parts(plain_text as *const u8, plain_bytes) };
let lit_msg = Message::new_literal_bytes("", bytes);
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.into_iter()
@@ -204,10 +209,9 @@ pub fn dc_pgp_pk_encrypt(
let mut rng = thread_rng();
// TODO: measure time
// TODO: better error handling
let encrypted_msg = if let Some(private_key) = private_key_for_signing {
let skey: &SignedSecretKey = private_key
.try_into()
.map_err(|_| format_err!("Invalid private key"))?;
let skey: &SignedSecretKey = private_key.try_into().unwrap();
lit_msg
.sign(skey, || "".into(), Default::default())
@@ -217,84 +221,110 @@ pub fn dc_pgp_pk_encrypt(
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys)
};
let msg = encrypted_msg?;
let encoded_msg = msg.to_armored_string(None)?;
Ok(encoded_msg)
encrypted_msg
.and_then(|msg| msg.to_armored_string(None))
.ok()
}
pub fn dc_pgp_pk_decrypt(
ctext: &[u8],
ctext: *const libc::c_void,
ctext_bytes: size_t,
private_keys_for_decryption: &Keyring,
public_keys_for_validation: &Keyring,
ret_signature_fingerprints: Option<&mut HashSet<String>>,
) -> Result<Vec<u8>, Error> {
let (msg, _) = Message::from_armor_single(Cursor::new(ctext))?;
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
.keys()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
})
.collect();
) -> Option<Vec<u8>> {
assert!(!ctext.is_null() && ctext_bytes > 0, "invalid input");
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
ensure!(!msgs.is_empty(), "No valid messages found");
let ctext = unsafe { std::slice::from_raw_parts(ctext as *const u8, ctext_bytes) };
let dec_msg = &msgs[0];
// TODO: proper error handling
if let Ok((msg, _)) = Message::from_armor_single(Cursor::new(ctext)) {
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
.keys()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
})
.collect();
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
if !public_keys_for_validation.keys().is_empty() {
let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation
.keys()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
})
.collect();
msg.decrypt(|| "".into(), || "".into(), &skeys[..])
.and_then(|(mut decryptor, _)| {
// TODO: how to handle the case when we detect multiple messages?
decryptor.next().expect("no message")
})
.and_then(|dec_msg| {
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
if !public_keys_for_validation.keys().is_empty() {
let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation
.keys()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
})
.collect();
for pkey in &pkeys {
if dec_msg.verify(&pkey.primary_key).is_ok() {
let fp = hex::encode_upper(pkey.fingerprint());
ret_signature_fingerprints.insert(fp);
for pkey in &pkeys {
if dec_msg.verify(&pkey.primary_key).is_ok() {
let fp = hex::encode_upper(pkey.fingerprint());
ret_signature_fingerprints.insert(fp);
}
}
}
}
}
}
}
match dec_msg.get_content()? {
Some(content) => Ok(content),
None => bail!("Decrypted message is empty"),
dec_msg.get_content()
})
.ok()
.and_then(|content| content)
} else {
None
}
}
/// Symmetric encryption.
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
pub fn dc_pgp_symm_encrypt(
passphrase: *const libc::c_char,
plain: *const libc::c_void,
plain_bytes: size_t,
) -> Option<String> {
assert!(!passphrase.is_null(), "invalid passphrase");
assert!(!plain.is_null() && !plain_bytes > 0, "invalid input");
let pw = unsafe { CStr::from_ptr(passphrase).to_str().unwrap() };
let bytes = unsafe { std::slice::from_raw_parts(plain as *const u8, plain_bytes) };
let mut rng = thread_rng();
let lit_msg = Message::new_literal_bytes("", plain);
let lit_msg = Message::new_literal_bytes("", bytes);
let s2k = StringToKey::new_default(&mut rng);
let msg =
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into())?;
let msg = lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || pw.into());
let encoded_msg = msg.to_armored_string(None)?;
Ok(encoded_msg)
msg.and_then(|msg| msg.to_armored_string(None)).ok()
}
/// Symmetric decryption.
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Error> {
let enc_msg = Message::from_bytes(Cursor::new(ctext))?;
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
pub fn dc_pgp_symm_decrypt(
passphrase: *const libc::c_char,
ctext: *const libc::c_void,
ctext_bytes: size_t,
) -> Option<Vec<u8>> {
assert!(!passphrase.is_null(), "invalid passphrase");
assert!(!ctext.is_null() && !ctext_bytes > 0, "invalid input");
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
ensure!(!msgs.is_empty(), "No valid messages found");
let pw = unsafe { CStr::from_ptr(passphrase).to_str().unwrap() };
let bytes = unsafe { std::slice::from_raw_parts(ctext as *const u8, ctext_bytes) };
match msgs[0].get_content()? {
Some(content) => Ok(content),
None => bail!("Decrypted message is empty"),
}
let enc_msg = Message::from_bytes(Cursor::new(bytes));
enc_msg
.and_then(|msg| {
let mut decryptor = msg
.decrypt_with_password(|| pw.into())
.expect("failed decryption");
decryptor.next().expect("no message")
})
.and_then(|msg| msg.get_content())
.ok()
.and_then(|content| content)
}

View File

@@ -37,7 +37,7 @@ impl Into<Lot> for Error {
pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
let qr = qr.as_ref();
info!(context, "Scanned QR code: {}", qr);
info!(context, 0, "Scanned QR code: {}", qr);
if qr.starts_with(OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr)
@@ -70,14 +70,19 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
None => return format_err!("Invalid OPENPGP4FPR found").into(),
};
dbg!(fingerprint);
dbg!(fragment);
// replace & with \n to match expected param format
let fragment = fragment.replace('&', "\n");
dbg!(&fragment);
// Then parse the parameters
let param: Params = match fragment.parse() {
Ok(params) => params,
Err(err) => return err.into(),
};
dbg!(&param);
let addr = if let Some(addr) = param.get(Param::Forwarded) {
match normalize_address(addr) {

View File

@@ -1,16 +1,17 @@
use mmime::mailimf_types::*;
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
use std::ptr;
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat};
use crate::config::*;
use crate::configure::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::events::Event;
use crate::key::*;
use crate::lot::LotState;
use crate::message::*;
@@ -19,32 +20,39 @@ use crate::peerstate::*;
use crate::qr::check_qr;
use crate::stock::StockMessage;
use crate::token;
use crate::types::*;
pub const NON_ALPHANUMERIC_WITHOUT_DOT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'.');
macro_rules! joiner_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
macro_rules! progress {
($context:tt, $event:expr, $contact_id:expr, $progress:expr) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::SecurejoinJoinerProgress {
contact_id: $contact_id,
progress: $progress,
});
$context.call_cb($event, $contact_id as uintptr_t, $progress as uintptr_t);
};
}
macro_rules! joiner_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
progress!(
$context,
Event::SECUREJOIN_JOINER_PROGRESS,
$contact_id,
$progress
);
};
}
macro_rules! inviter_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
progress!(
$context,
Event::SECUREJOIN_INVITER_PROGRESS,
$contact_id,
$progress
);
$context.call_cb($crate::events::Event::SecurejoinInviterProgress {
contact_id: $contact_id,
progress: $progress,
});
};
}
@@ -63,7 +71,7 @@ macro_rules! get_qr_attr {
};
}
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<String> {
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Option<String> {
/* =========================================================
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
@@ -74,10 +82,10 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
ensure_secret_key_exists(context).ok();
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
let self_addr = match context.get_config(Config::ConfiguredAddr) {
let self_addr = match context.sql.get_config(context, "configured_addr") {
Some(addr) => addr,
None => {
error!(context, "Not configured, cannot generate QR code.",);
error!(context, 0, "Not configured, cannot generate QR code.",);
return None;
}
};
@@ -115,7 +123,10 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
&auth,
))
} else {
error!(context, "Cannot get QR-code for chat-id {}", group_chat_id,);
error!(
context,
0, "Cannot get QR-code for chat-id {}", group_chat_id,
);
return None;
}
} else {
@@ -125,13 +136,13 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
))
};
info!(context, "Generated QR code: {}", qr.as_ref().unwrap());
info!(context, 0, "Generated QR code: {}", qr.as_ref().unwrap());
qr
}
fn get_self_fingerprint(context: &Context) -> Option<String> {
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
if let Some(self_addr) = context.sql.get_config(context, "configured_addr") {
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
return Some(key.fingerprint());
}
@@ -139,7 +150,7 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
None
}
pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
pub fn dc_join_securejoin(context: &Context, qr: &str) -> uint32_t {
let cleanup =
|context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| {
let mut bob = context.bob.write().unwrap();
@@ -162,16 +173,16 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
if ongoing_allocated {
dc_free_ongoing(context);
}
ret_chat_id as u32
ret_chat_id as uint32_t
};
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================== */
let mut contact_chat_id: u32 = 0;
let mut contact_chat_id: uint32_t = 0;
let mut join_vg: bool = false;
info!(context, "Requesting secure-join ...",);
info!(context, 0, "Requesting secure-join ...",);
ensure_secret_key_exists(context).ok();
if !dc_alloc_ongoing(context) {
return cleanup(&context, contact_chat_id, false, join_vg);
@@ -179,12 +190,12 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
let qr_scan = check_qr(context, &qr);
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
{
error!(context, "Unknown QR code.",);
error!(context, 0, "Unknown QR code.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
contact_chat_id = chat::create_by_contact_id(context, qr_scan.id).unwrap_or_default();
if contact_chat_id == 0 {
error!(context, "Unknown contact.",);
error!(context, 0, "Unknown contact.",);
return cleanup(&context, contact_chat_id, true, join_vg);
}
if check_exit(context) {
@@ -210,7 +221,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
.unwrap(),
contact_chat_id,
) {
info!(context, "Taking protocol shortcut.");
info!(context, 0, "Taking protocol shortcut.");
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
let own_fingerprint = get_self_fingerprint(context).unwrap();
@@ -260,13 +271,13 @@ fn check_exit(context: &Context) -> bool {
fn send_handshake_msg(
context: &Context,
contact_chat_id: u32,
contact_chat_id: uint32_t,
step: &str,
param2: impl AsRef<str>,
fingerprint: Option<String>,
grpid: impl AsRef<str>,
) {
let mut msg = dc_msg_new_untyped();
let mut msg = unsafe { dc_msg_new_untyped(context) };
msg.type_0 = Viewtype::Text;
msg.text = Some(format!("Secure-Join: {}", step));
msg.hidden = true;
@@ -297,7 +308,7 @@ fn send_handshake_msg(
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
}
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> uint32_t {
let contacts = chat::get_chat_contacts(context, contact_chat_id);
if contacts.len() == 1 {
contacts[0]
@@ -332,15 +343,15 @@ fn fingerprint_equals_sender(
/* library private: secure-join */
pub fn handle_securejoin_handshake(
context: &Context,
mimeparser: &MimeParser,
contact_id: u32,
mimeparser: &dc_mimeparser_t,
contact_id: uint32_t,
) -> libc::c_int {
let own_fingerprint: String;
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return 0;
}
let step = match mimeparser.lookup_optional_field("Secure-Join") {
let step = match lookup_field(mimeparser, "Secure-Join") {
Some(s) => s,
None => {
return 0;
@@ -348,7 +359,7 @@ pub fn handle_securejoin_handshake(
};
info!(
context,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
0, ">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step,
);
let (contact_chat_id, contact_chat_id_blocked) =
chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default();
@@ -369,18 +380,18 @@ pub fn handle_securejoin_handshake(
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
let invitenumber = match mimeparser.lookup_optional_field("Secure-Join-Invitenumber") {
let invitenumber = match lookup_field(mimeparser, "Secure-Join-Invitenumber") {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
warn!(context, 0, "Secure-join denied (invitenumber missing).",);
return ret;
}
};
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
warn!(context, "Secure-join denied (bad invitenumber).",);
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
return ret;
}
info!(context, "Secure-join requested.",);
info!(context, 0, "Secure-join requested.",);
inviter_progress!(context, contact_id, 300);
send_handshake_msg(
@@ -402,7 +413,7 @@ pub fn handle_securejoin_handshake(
};
if cond {
warn!(context, "auth-required message out of sync.",);
warn!(context, 0, "auth-required message out of sync.",);
// no error, just aborted somehow or a mail from another handshake
return ret;
}
@@ -431,7 +442,7 @@ pub fn handle_securejoin_handshake(
end_bobs_joining(context, DC_BOB_ERROR);
return ret;
}
info!(context, "Fingerprint verified.",);
info!(context, 0, "Fingerprint verified.",);
own_fingerprint = get_self_fingerprint(context).unwrap();
joiner_progress!(context, contact_id, 400);
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
@@ -456,7 +467,7 @@ pub fn handle_securejoin_handshake(
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") {
let fingerprint = match lookup_field(mimeparser, "Secure-Join-Fingerprint") {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
@@ -483,9 +494,9 @@ pub fn handle_securejoin_handshake(
);
return ret;
}
info!(context, "Fingerprint verified.",);
info!(context, 0, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") {
let auth_0 = match lookup_field(mimeparser, "Secure-Join-Auth") {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
@@ -509,17 +520,15 @@ pub fn handle_securejoin_handshake(
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
info!(context, "Auth verified.",);
info!(context, 0, "Auth verified.",);
secure_connection_established(context, contact_chat_id);
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
emit_event!(context, Event::CONTACTS_CHANGED, contact_id, 0);
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = mimeparser
.lookup_optional_field("Secure-Join-Group")
.unwrap_or_default();
let field_grpid = lookup_field(mimeparser, "Secure-Join-Group").unwrap_or_default();
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
if group_chat_id == 0 {
error!(context, "Chat {} not found.", &field_grpid);
error!(context, 0, "Chat {} not found.", &field_grpid);
return ret;
} else {
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, 0x1i32);
@@ -534,7 +543,7 @@ pub fn handle_securejoin_handshake(
ret = DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
}
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
info!(context, "Message belongs to a different handshake.",);
info!(context, 0, "Message belongs to a different handshake.",);
return ret;
}
let cond = {
@@ -545,7 +554,7 @@ pub fn handle_securejoin_handshake(
if cond {
warn!(
context,
"Message out of sync or belongs to a different handshake.",
0, "Message out of sync or belongs to a different handshake.",
);
return ret;
}
@@ -584,12 +593,11 @@ pub fn handle_securejoin_handshake(
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
.lookup_optional_field("Chat-Group-Member-Added")
.unwrap_or_default();
emit_event!(context, Event::CONTACTS_CHANGED, 0, 0);
let cg_member_added =
lookup_field(mimeparser, "Chat-Group-Member-Added").unwrap_or_default();
if join_vg && !addr_equals_self(context, cg_member_added) {
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
info!(context, 0, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return ret;
}
secure_connection_established(context, contact_chat_id);
@@ -612,19 +620,19 @@ pub fn handle_securejoin_handshake(
==== Step 8 in "Out-of-band verified groups" protocol ====
============================================================ */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified(context) == VerifiedStatus::Unverified {
warn!(context, "vg-member-added-received invalid.",);
if contact.is_verified() == VerifiedStatus::Unverified {
warn!(context, 0, "vg-member-added-received invalid.",);
return ret;
}
inviter_progress!(context, contact_id, 800);
inviter_progress!(context, contact_id, 1000);
} else {
warn!(context, "vg-member-added-received invalid.",);
warn!(context, 0, "vg-member-added-received invalid.",);
return ret;
}
}
_ => {
warn!(context, "invalid step: {}", step);
warn!(context, 0, "invalid step: {}", step);
}
}
if ret == DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
@@ -638,8 +646,8 @@ fn end_bobs_joining(context: &Context, status: libc::c_int) {
dc_stop_ongoing_process(context);
}
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id);
fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) {
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let addr = if let Ok(ref contact) = contact {
contact.get_addr()
@@ -648,10 +656,32 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
}
fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, details: &str) {
fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> Option<String> {
let field: *mut mailimf_field = dc_mimeparser_lookup_field(mimeparser, key);
unsafe {
let mut value: *const libc::c_char = ptr::null();
if field.is_null()
|| (*field).fld_type != MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
|| (*field).fld_data.fld_optional_field.is_null()
|| {
value = (*(*field).fld_data.fld_optional_field).fld_value;
value.is_null()
}
{
return None;
}
Some(as_str(value).to_string())
}
}
fn could_not_establish_secure_connection(
context: &Context,
contact_chat_id: uint32_t,
details: &str,
) {
let contact_id = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let msg = context.stock_string_repl_str(
@@ -664,7 +694,7 @@ fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32
);
chat::add_device_msg(context, contact_chat_id, &msg);
error!(context, "{} ({})", &msg, details);
error!(context, 0, "{} ({})", &msg, details);
}
fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Result<(), Error> {
@@ -688,15 +718,18 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
* Tools: Misc.
******************************************************************************/
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
fn encrypted_and_signed(
mimeparser: &dc_mimeparser_t,
expected_fingerprint: impl AsRef<str>,
) -> bool {
if !mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
warn!(mimeparser.context, 0, "Message not encrypted.",);
false
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
warn!(mimeparser.context, "Message not signed.",);
warn!(mimeparser.context, 0, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
warn!(mimeparser.context, 0, "Fingerprint for comparison missing.",);
false
} else if !mimeparser
.e2ee_helper
@@ -705,6 +738,7 @@ fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRe
{
warn!(
mimeparser.context,
0,
"Message does not match expected fingerprint {}.",
expected_fingerprint.as_ref(),
);
@@ -723,10 +757,11 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = context
.sql
.query_get_value(
.query_row_col(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
0,
)
.unwrap_or_default();
if contact_id > 0 {
@@ -741,7 +776,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
}
}
}

View File

@@ -1,15 +1,13 @@
use lettre::smtp::client::net::*;
use lettre::*;
use crate::constants::Event;
use crate::constants::*;
use crate::context::Context;
use crate::events::Event;
use crate::login_param::LoginParam;
use crate::oauth2::*;
#[derive(DebugStub)]
pub struct Smtp {
#[debug_stub(some = "SmtpTransport")]
transport: Option<lettre::smtp::SmtpTransport>,
transport_connected: bool,
/// Email address we are sending from.
@@ -47,12 +45,12 @@ impl Smtp {
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
if self.is_connected() {
warn!(context, "SMTP already connected.");
warn!(context, 0, "SMTP already connected.");
return true;
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
log_event!(context, Event::ERROR_NETWORK, 0, "SMTP bad parameters.",);
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
@@ -82,7 +80,7 @@ impl Smtp {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, 0);
if access_token.is_none() {
return false;
}
@@ -111,14 +109,17 @@ impl Smtp {
.credentials(creds)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
self.transport = Some(client.transport());
context.call_cb(Event::SmtpConnected(format!(
log_event!(
context,
Event::SMTP_CONNECTED,
0,
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
);
true
}
Err(err) => {
warn!(context, "SMTP: failed to establish connection {:?}", err);
warn!(context, 0, "SMTP: failed to establish connection {:?}", err);
false
}
}
@@ -140,14 +141,17 @@ impl Smtp {
match transport.send(mail) {
Ok(_) => {
context.call_cb(Event::SmtpMessageSent(
"Message was sent to SMTP server".into(),
));
log_event!(
context,
Event::SMTP_MESSAGE_SENT,
0,
"Message was sent to SMTP server",
);
self.transport_connected = true;
1
}
Err(err) => {
warn!(context, "SMTP failed to send message: {}", err);
warn!(context, 0, "SMTP failed to send message: {}", err);
self.error = Some(format!("{}", err));
0
}

View File

@@ -13,10 +13,8 @@ use crate::peerstate::*;
const DC_OPEN_READONLY: usize = 0x01;
/// A wrapper around the underlying Sqlite3 object.
#[derive(DebugStub)]
pub struct Sql {
pool: RwLock<Option<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>>>,
#[debug_stub = "ThreadLocal<String>"]
in_use: Arc<ThreadLocal<String>>,
}
@@ -37,7 +35,7 @@ impl Sql {
self.in_use.remove();
// drop closes the connection
info!(context, "Database closed.");
info!(context, 0, "Database closed.");
}
// return true on success, false on failure
@@ -157,13 +155,19 @@ impl Sql {
.unwrap_or_default()
}
pub fn query_get_value<P, T>(&self, context: &Context, query: &str, params: P) -> Option<T>
pub fn query_row_col<P, T>(
&self,
context: &Context,
query: &str,
params: P,
column: usize,
) -> Option<T>
where
P: IntoIterator,
P::Item: rusqlite::ToSql,
T: rusqlite::types::FromSql,
{
match self.query_row(query, params, |row| row.get::<_, T>(0)) {
match self.query_row(query, params, |row| row.get::<_, T>(column)) {
Ok(res) => Some(res),
Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => None,
Err(Error::Sql(rusqlite::Error::InvalidColumnType(
@@ -172,7 +176,7 @@ impl Sql {
rusqlite::types::Type::Null,
))) => None,
Err(err) => {
error!(context, "sql: Failed query_row: {}", err);
error!(context, 0, "sql: Failed query_row: {}", err);
None
}
}
@@ -189,7 +193,7 @@ impl Sql {
value: Option<&str>,
) -> Result<()> {
if !self.is_open() {
error!(context, "set_config(): Database not ready.");
error!(context, 0, "set_config(): Database not ready.");
return Err(Error::SqlNoConnection);
}
@@ -223,7 +227,7 @@ impl Sql {
match res {
Ok(_) => Ok(()),
Err(err) => {
error!(context, "set_config(): Cannot change value. {:?}", &err);
error!(context, 0, "set_config(): Cannot change value. {:?}", &err);
Err(err.into())
}
}
@@ -234,10 +238,11 @@ impl Sql {
if !self.is_open() || key.as_ref().is_empty() {
return None;
}
self.query_get_value(
self.query_row_col(
context,
"SELECT value FROM config WHERE keyname=?;",
params![key.as_ref()],
0,
)
}
@@ -254,20 +259,6 @@ impl Sql {
self.get_config(context, key).and_then(|s| s.parse().ok())
}
pub fn get_config_bool(&self, context: &Context, key: impl AsRef<str>) -> bool {
// Not the most obvious way to encode bool as string, but it is matter
// of backward compatibility.
self.get_config_int(context, key).unwrap_or_default() > 0
}
pub fn set_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
where
T: AsRef<str>,
{
let value = if value { Some("1") } else { None };
self.set_config(context, key, value)
}
pub fn set_config_int64(
&self,
context: &Context,
@@ -312,6 +303,7 @@ fn open(
if sql.is_open() {
error!(
context,
0,
"Cannot open, database \"{:?}\" already opened.",
dbfile.as_ref(),
);
@@ -345,6 +337,7 @@ fn open(
if !sql.table_exists("config") {
info!(
context,
0,
"First time init: creating tables in {:?}.",
dbfile.as_ref(),
);
@@ -460,6 +453,7 @@ fn open(
{
error!(
context,
0,
"Cannot create tables in new database \"{:?}\".",
dbfile.as_ref(),
);
@@ -680,7 +674,7 @@ fn open(
sql.set_config_int(context, "dbversion", 46)?;
}
if dbversion < 47 {
info!(context, "[migration] v47");
info!(context, 0, "[migration] v47");
sql.execute(
"ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;",
params![],
@@ -689,7 +683,7 @@ fn open(
sql.set_config_int(context, "dbversion", 47)?;
}
if dbversion < 48 {
info!(context, "[migration] v48");
info!(context, 0, "[migration] v48");
sql.execute(
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
params![],
@@ -699,7 +693,7 @@ fn open(
sql.set_config_int(context, "dbversion", 48)?;
}
if dbversion < 49 {
info!(context, "[migration] v49");
info!(context, 0, "[migration] v49");
sql.execute(
"ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
params![],
@@ -708,7 +702,7 @@ fn open(
sql.set_config_int(context, "dbversion", 49)?;
}
if dbversion < 50 {
info!(context, "[migration] v50");
info!(context, 0, "[migration] v50");
if 0 != exists_before_update {
sql.set_config_int(context, "show_emails", 2)?;
}
@@ -716,7 +710,7 @@ fn open(
sql.set_config_int(context, "dbversion", 50)?;
}
if dbversion < 53 {
info!(context, "[migration] v53");
info!(context, 0, "[migration] v53");
sql.execute(
"CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);",
params![]
@@ -749,7 +743,7 @@ fn open(
sql.set_config_int(context, "dbversion", 53)?;
}
if dbversion < 54 {
info!(context, "[migration] v54");
info!(context, 0, "[migration] v54");
sql.execute(
"ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;",
params![],
@@ -789,11 +783,11 @@ fn open(
// for newer versions, we copy files always to the blob directory and store relative paths.
// this snippet converts older databases and can be removed after some time.
info!(context, "[open] update file paths");
info!(context, 0, "[open] update file paths");
let repl_from = sql
.get_config(context, "backup_for")
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
.unwrap_or_else(|| to_string(context.get_blobdir()));
let repl_from = dc_ensure_no_slash_safe(&repl_from);
sql.execute(
@@ -816,7 +810,7 @@ fn open(
}
}
info!(context, "Opened {:?}.", dbfile.as_ref(),);
info!(context, 0, "Opened {:?}.", dbfile.as_ref(),);
Ok(())
}
@@ -831,6 +825,7 @@ where
Err(err) => {
error!(
context,
0,
"execute failed: {:?} for {}",
&err,
querystr.as_ref()
@@ -847,6 +842,7 @@ pub fn try_execute(context: &Context, sql: &Sql, querystr: impl AsRef<str>) -> R
Err(err) => {
warn!(
context,
0,
"Try-execute for \"{}\" failed: {}",
querystr.as_ref(),
&err,
@@ -890,7 +886,7 @@ pub fn get_rowid_with_conn(
Err(err) => {
error!(
context,
"sql: Failed to retrieve rowid: {} in {}", err, query
0, "sql: Failed to retrieve rowid: {} in {}", err, query
);
0
}
@@ -937,7 +933,7 @@ pub fn get_rowid2_with_conn(
) {
Ok(id) => id,
Err(err) => {
error!(context, "sql: Failed to retrieve rowid2: {}", err);
error!(context, 0, "sql: Failed to retrieve rowid2: {}", err);
0
}
}
@@ -947,7 +943,7 @@ pub fn housekeeping(context: &Context) {
let mut files_in_use = HashSet::new();
let mut unreferenced_count = 0;
info!(context, "Start housekeeping...");
info!(context, 0, "Start housekeeping...");
maybe_add_from_param(
context,
&mut files_in_use,
@@ -987,12 +983,12 @@ pub fn housekeeping(context: &Context) {
},
)
.unwrap_or_else(|err| {
warn!(context, "sql: failed query: {}", err);
warn!(context, 0, "sql: failed query: {}", err);
});
info!(context, "{} files in use.", files_in_use.len(),);
info!(context, 0, "{} files in use.", files_in_use.len(),);
/* go through directory and delete unused files */
let p = context.get_blobdir();
let p = std::path::Path::new(as_str(context.get_blobdir()));
match std::fs::read_dir(p) {
Ok(dir_handle) => {
/* avoid deletion of files that are just created to build a message object */
@@ -1029,6 +1025,7 @@ pub fn housekeeping(context: &Context) {
if recently_created || recently_modified || recently_accessed {
info!(
context,
0,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name(),
@@ -1040,6 +1037,7 @@ pub fn housekeeping(context: &Context) {
}
info!(
context,
0,
"Housekeeping: Deleting unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name()
@@ -1051,14 +1049,15 @@ pub fn housekeeping(context: &Context) {
Err(err) => {
warn!(
context,
0,
"Housekeeping: Cannot open {}. ({})",
context.get_blobdir().display(),
as_str(context.get_blobdir()),
err
);
}
}
info!(context, "Housekeeping done.",);
info!(context, 0, "Housekeeping done.",);
}
fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
@@ -1106,7 +1105,7 @@ fn maybe_add_from_param(
},
)
.unwrap_or_else(|err| {
warn!(context, "sql: failed to add_from_param: {}", err);
warn!(context, 0, "sql: failed to add_from_param: {}", err);
});
}

View File

@@ -3,10 +3,10 @@ use std::borrow::Cow;
use strum::EnumProperty;
use strum_macros::EnumProperty;
use crate::constants::Event;
use crate::contact::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::events::Event;
use libc::free;
/// Stock strings
@@ -128,7 +128,7 @@ impl Context {
/// translation, then this string will be returned. Otherwise a
/// default (English) string is returned.
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
let ptr = self.call_cb(Event::GET_STRING, id as usize, 0) as *mut libc::c_char;
if ptr.is_null() {
Cow::Borrowed(id.fallback())
} else {
@@ -140,14 +140,12 @@ impl Context {
/// Return stock string, replacing placeholders with provided string.
///
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// placeholders with the provided string.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
pub fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef<str>) -> String {
self.stock_str(id)
.replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1)
.replacen("%1$@", insert.as_ref(), 1)
}
/// Return stock string, replacing placeholders with provided int.
@@ -160,10 +158,9 @@ impl Context {
/// Return stock string, replacing 2 placeholders with provided string.
///
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// placeholders with the string in `insert` and does the same for
/// `%2$s`, `%2$d` and `%2$@` for `insert2`.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
/// `%2$s` and `%2$d` for `insert2`.
fn stock_string_repl_str2(
&self,
id: StockMessage,
@@ -173,10 +170,8 @@ impl Context {
self.stock_str(id)
.replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1)
.replacen("%1$@", insert.as_ref(), 1)
.replacen("%2$s", insert2.as_ref(), 1)
.replacen("%2$d", insert2.as_ref(), 1)
.replacen("%2$@", insert2.as_ref(), 1)
}
/// Return some kind of stock message
@@ -237,8 +232,11 @@ mod tests {
use super::*;
use crate::test_utils::*;
use std::ffi::CString;
use crate::constants::DC_CONTACT_ID_SELF;
use libc::uintptr_t;
use crate::context::dc_context_new;
use crate::types::uintptr_t;
use num_traits::ToPrimitive;
@@ -255,32 +253,36 @@ mod tests {
#[test]
fn test_stock_str() {
let t = dummy_context();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
}
fn test_stock_str_no_fallback_cb(_ctx: &Context, evt: Event) -> uintptr_t {
match evt {
Event::GetString {
id: StockMessage::NoMessages,
..
} => unsafe { "Hello there".strdup() as usize },
_ => 0,
unsafe extern "C" fn test_stock_str_no_fallback_cb(
_ctx: &Context,
evt: Event,
d1: uintptr_t,
_d2: uintptr_t,
) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() {
let tmp = CString::new("Hello there").unwrap();
dc_strdup(tmp.as_ptr()) as usize
} else {
0
}
}
#[test]
fn test_stock_str_no_fallback() {
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
let t = test_context(Some(test_stock_str_no_fallback_cb));
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
}
#[test]
fn test_stock_string_repl_str() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
// uses %1$s substitution
assert_eq!(
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
ctx.stock_string_repl_str(StockMessage::Member, "42"),
"42 member(s)"
);
// We have no string using %1$d to test...
@@ -288,38 +290,36 @@ mod tests {
#[test]
fn test_stock_string_repl_int() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
ctx.stock_string_repl_int(StockMessage::Member, 42),
"42 member(s)"
);
}
#[test]
fn test_stock_string_repl_str2() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
"Response from foo: bar"
);
}
#[test]
fn test_stock_system_msg_simple() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
"Location streaming enabled."
)
}
#[test]
fn test_stock_system_msg_add_member_by_me() {
let t = dummy_context();
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
t.ctx.stock_system_msg(
ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",

View File

@@ -2,14 +2,16 @@
//!
//! This module is only compiled for test runs.
use std::ffi::CStr;
use libc::uintptr_t;
use tempfile::{tempdir, TempDir};
use crate::config::Config;
use crate::constants::KeyType;
use crate::context::{Context, ContextCallback};
use crate::events::Event;
use crate::constants::{Event, KeyType};
use crate::context::{dc_context_new, dc_open, Context};
use crate::key;
use crate::types::dc_callback_t;
/// A Context and temporary directory.
///
@@ -26,15 +28,18 @@ pub struct TestContext {
/// "db.sqlite" in the [TestContext.dir] directory.
///
/// [Context]: crate::context::Context
pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let cb: Box<ContextCallback> = match callback {
Some(cb) => cb,
None => Box::new(|_, _| 0),
};
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
unsafe {
let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
"Failed to open {}",
dbfile.display(),
);
TestContext { ctx: ctx, dir: dir }
}
}
/// Return a dummy [TestContext].
@@ -46,11 +51,17 @@ pub fn dummy_context() -> TestContext {
test_context(None)
}
pub fn logging_cb(_ctx: &Context, evt: Event) -> uintptr_t {
pub unsafe extern "C" fn logging_cb(
_ctx: &Context,
evt: Event,
_d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap();
match evt {
Event::Info(msg) => println!("I: {}", msg),
Event::Warning(msg) => println!("W: {}", msg),
Event::Error(msg) => println!("E: {}", msg),
Event::INFO => println!("I: {}", to_str(d2)),
Event::WARNING => println!("W: {}", to_str(d2)),
Event::ERROR => println!("E: {}", to_str(d2)),
_ => (),
}
0

View File

@@ -35,10 +35,11 @@ pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String
}
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: u32) -> Option<String> {
context.sql.query_get_value::<_, String>(
context.sql.query_row_col::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespace, foreign_id as i32],
0,
)
}

View File

@@ -6,15 +6,13 @@ import os
import re
if __name__ == "__main__":
if Path('src/top_evil_rs.py').exists():
os.chdir('src')
filestats = []
for fn in Path(".").glob("**/*.rs"):
s = fn.read_text()
s = re.sub(r"(?m)///.*$", "", s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
gotoblocks = s.count("ok_to_continue") + s.count('OK_TO_CONTINUE')
gotoblocks = s.count("ok_to_continue")
filestats.append((fn, unsafe, free, gotoblocks))
sum_unsafe, sum_free, sum_gotoblocks = 0, 0, 0

43
src/types.rs Normal file
View File

@@ -0,0 +1,43 @@
#![allow(non_camel_case_types)]
use crate::constants::Event;
use crate::context::Context;
pub use mmime::clist::*;
pub use rusqlite::ffi::*;
/// Callback function that should be given to dc_context_new().
///
/// @memberof Context
/// @param context The context object as returned by dc_context_new().
/// @param event one of the @ref DC_EVENT constants
/// @param data1 depends on the event parameter
/// @param data2 depends on the event parameter
/// @return return 0 unless stated otherwise in the event parameter documentation
pub type dc_callback_t =
unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t;
pub type dc_receive_imf_t = unsafe fn(
_: &Context,
_: *const libc::c_char,
_: size_t,
_: &str,
_: uint32_t,
_: uint32_t,
) -> ();
/* Purpose: Reading from IMAP servers with no dependencies to the database.
Context is only used for logging and to get information about
the online state. */
pub type dc_precheck_imf_t =
unsafe fn(_: &Context, _: *const libc::c_char, _: &str, _: u32) -> libc::c_int;
pub type dc_set_config_t = fn(_: &Context, _: &str, _: Option<&str>) -> ();
pub type dc_get_config_t = fn(_: &Context, _: &str) -> Option<String>;
pub type int32_t = i32;
pub type uintptr_t = libc::uintptr_t;
pub type size_t = libc::size_t;
pub type uint32_t = libc::c_uint;
pub type uint8_t = libc::c_uchar;
pub type uint16_t = libc::c_ushort;
pub type uint64_t = u64;

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,24 @@
//! Stress some functions for testing; if used as a lib, this file is obsolete.
use std::collections::HashSet;
use std::path::PathBuf;
use std::ffi::CString;
use std::ptr;
use tempfile::{tempdir, TempDir};
use deltachat::chat::{self, Chat};
use deltachat::config;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_tools::*;
use deltachat::key::*;
use deltachat::keyring::*;
use deltachat::oauth2::*;
use deltachat::pgp::*;
use deltachat::types::*;
use deltachat::x::*;
use deltachat::Event;
use libc;
/* some data used for testing
@@ -30,67 +32,103 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
as *const u8 as *const libc::c_char;
unsafe fn stress_functions(context: &Context) {
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7);
let abs_path = context
.get_blobdir()
.join("foobar")
.to_string_lossy()
.to_string();
assert!(dc_is_blobdir_path(context, &abs_path));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
assert!(dc_file_exist(context, &abs_path));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let mut buf: *mut libc::c_void = ptr::null_mut();
let mut buf_bytes: libc::size_t = 0;
assert_eq!(
dc_read_file(
if 0 != dc_is_open(context) {
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
dc_write_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
&mut buf,
&mut buf_bytes,
),
1
);
assert_eq!(buf_bytes, 7);
assert_eq!(
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
"content"
);
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(
dc_get_filebytes(context, "$BLOBDIR/foobar",),
7i32 as libc::c_ulonglong
);
free(buf as *mut _);
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
let abs_path: *mut libc::c_char = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
b"foobar\x00" as *const u8 as *const libc::c_char,
);
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
assert!(dc_file_exist(context, as_path(abs_path)));
free(abs_path as *mut libc::c_void);
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
assert!(dc_write_file(context, &fn0, b"content"));
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
let mut buf: *mut libc::c_void = ptr::null_mut();
let mut buf_bytes: size_t = 0;
assert!(dc_delete_file(context, &fn0));
assert_eq!(
dc_read_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
&mut buf,
&mut buf_bytes,
),
1
);
assert_eq!(buf_bytes, 7);
assert_eq!(
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
"content"
);
free(buf as *mut _);
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn0.is_null());
assert_eq!(
strcmp(
fn0,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
dc_write_file(
context,
fn0,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn1.is_null());
assert_eq!(
strcmp(
fn1,
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(dc_delete_file(context, as_path(fn0)));
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
}
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
@@ -424,114 +462,175 @@ unsafe fn stress_functions(context: &Context) {
#[test]
#[ignore] // is too expensive
fn test_encryption_decryption() {
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
unsafe {
let mut bad_data: [libc::c_uchar; 4096] = [0; 4096];
let mut i_0: libc::c_int = 0i32;
while i_0 < 4096i32 {
bad_data[i_0 as usize] = (i_0 & 0xffi32) as libc::c_uchar;
i_0 += 1
}
let mut j: libc::c_int = 0i32;
private_key.split_key().unwrap();
while j < 4096 / 40 {
let bad_key = Key::from_binary(
&mut *bad_data.as_mut_ptr().offset(j as isize) as *const u8,
4096 / 2 + j,
if 0 != j & 1 {
KeyType::Public
} else {
KeyType::Private
},
);
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
assert!(bad_key.is_none());
j += 1
}
assert_ne!(public_key, public_key2);
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
let original_text = b"This is a test";
let mut keyring = Keyring::default();
keyring.add_owned(public_key.clone());
keyring.add_ref(&public_key2);
private_key.split_key().unwrap();
let ctext_signed = dc_pgp_pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
assert!(!ctext_signed.is_empty());
assert!(ctext_signed.starts_with("-----BEGIN PGP MESSAGE-----"));
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
let ctext_unsigned = dc_pgp_pk_encrypt(original_text, &keyring, None).unwrap();
assert!(!ctext_unsigned.is_empty());
assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----"));
assert_ne!(public_key, public_key2);
let mut keyring = Keyring::default();
keyring.add_owned(private_key);
let original_text: *const libc::c_char =
b"This is a test\x00" as *const u8 as *const libc::c_char;
let mut keyring = Keyring::default();
keyring.add_owned(public_key.clone());
keyring.add_ref(&public_key2);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let ctext = dc_pgp_pk_encrypt(
original_text as *const libc::c_void,
strlen(original_text),
&keyring,
Some(&private_key),
)
.unwrap();
let mut public_keyring2 = Keyring::default();
public_keyring2.add_owned(public_key2.clone());
assert!(!ctext.is_empty());
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
let mut valid_signatures: HashSet<String> = Default::default();
let ctext_signed_bytes = ctext.len();
let ctext_signed = CString::yolo(ctext);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
let ctext = dc_pgp_pk_encrypt(
original_text as *const libc::c_void,
strlen(original_text),
&keyring,
None,
)
.unwrap();
assert!(!ctext.is_empty());
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
assert_eq!(plain, original_text,);
assert_eq!(valid_signatures.len(), 1);
let ctext_unsigned_bytes = ctext.len();
let ctext_unsigned = CString::yolo(ctext);
valid_signatures.clear();
let mut keyring = Keyring::default();
keyring.add_owned(private_key);
let empty_keyring = Keyring::default();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&empty_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(valid_signatures.len(), 0);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
valid_signatures.clear();
let mut public_keyring2 = Keyring::default();
public_keyring2.add_owned(public_key2.clone());
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(valid_signatures.len(), 0);
let mut valid_signatures: HashSet<String> = Default::default();
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
public_keyring2.add_ref(&public_key);
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 1);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_bytes(),
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(plain, original_text);
assert_eq!(valid_signatures.len(), 1);
valid_signatures.clear();
valid_signatures.clear();
let empty_keyring = Keyring::default();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&empty_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 0);
let plain = dc_pgp_pk_decrypt(
ctext_unsigned.as_bytes(),
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
valid_signatures.clear();
assert_eq!(plain, original_text);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 0);
valid_signatures.clear();
valid_signatures.clear();
let mut keyring = Keyring::default();
keyring.add_ref(&private_key2);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
public_keyring2.add_ref(&public_key);
let plain =
dc_pgp_pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring2,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
assert_eq!(valid_signatures.len(), 1);
assert_eq!(plain, original_text);
valid_signatures.clear();
let plain = dc_pgp_pk_decrypt(
ctext_unsigned.as_ptr() as *const _,
ctext_unsigned_bytes,
&keyring,
&public_keyring,
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
valid_signatures.clear();
let mut keyring = Keyring::default();
keyring.add_ref(&private_key2);
let mut public_keyring = Keyring::default();
public_keyring.add_ref(&public_key);
let plain = dc_pgp_pk_decrypt(
ctext_signed.as_ptr() as *const _,
ctext_signed_bytes,
&keyring,
&public_keyring,
None,
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
}
}
fn cb(_context: &Context, _event: Event) -> libc::uintptr_t {
unsafe extern "C" fn cb(
_context: &Context,
_event: Event,
_data1: uintptr_t,
_data2: uintptr_t,
) -> uintptr_t {
0
}
@@ -541,16 +640,21 @@ struct TestContext {
dir: TempDir,
}
fn create_test_context() -> TestContext {
unsafe fn create_test_context() -> TestContext {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
"Failed to open {}",
dbfile.display()
);
TestContext { ctx: ctx, dir: dir }
}
#[test]
fn test_dc_get_oauth2_url() {
let ctx = create_test_context();
let ctx = unsafe { create_test_context() };
let addr = "dignifiedquire@gmail.com";
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
@@ -560,7 +664,7 @@ fn test_dc_get_oauth2_url() {
#[test]
fn test_dc_get_oauth2_addr() {
let ctx = create_test_context();
let ctx = unsafe { create_test_context() };
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
@@ -570,10 +674,10 @@ fn test_dc_get_oauth2_addr() {
#[test]
fn test_dc_get_oauth2_token() {
let ctx = create_test_context();
let ctx = unsafe { create_test_context() };
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false);
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
// this should fail as it is an invalid password
assert_eq!(res, None);
}
@@ -588,33 +692,50 @@ fn test_stress_tests() {
#[test]
fn test_get_contacts() {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
unsafe {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
}
#[test]
fn test_chat() {
let context = create_test_context();
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(contact1, 0);
unsafe {
let context = create_test_context();
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(contact1, 0);
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert!(chat_id > 9, "chat_id too small {}", chat_id);
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert!(chat_id > 9, "chat_id too small {}", chat_id);
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert_eq!(chat2_id, chat_id);
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
assert_eq!(chat2_id, chat_id);
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
assert_eq!(chat2.name, chat.name);
assert_eq!(chat2.name, chat.name);
}
}
#[test]
fn test_wrong_db() {
unsafe {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
std::fs::write(&dbfile, b"123").unwrap();
let res = dc_open(&mut ctx, dbfile.to_str().unwrap(), None);
assert!(!res);
}
}