Compare commits

..

4 Commits

88 changed files with 15983 additions and 19253 deletions

View File

@@ -1,4 +1,5 @@
version: 2.1
executors:
default:
docker:
@@ -12,7 +13,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -52,11 +53,10 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v0-{{ 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
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
- run: cargo update
- run: cargo fetch
- run: rustc +stable --version
@@ -67,7 +67,7 @@ jobs:
paths:
- crate
- save_cache:
key: cargo-v1-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -152,24 +152,14 @@ jobs:
- wheelhouse
upload_docs_wheels:
machine: true
machine: True
steps:
- checkout
- attach_workspace:
at: workspace
- run: pyenv global 3.5.2
- run: ls -laR workspace
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
clippy:
executor: default
steps:
- *restore-workspace
- *restore-cache
- run:
name: Run cargo clippy
command: cargo clippy --all
workflows:
version: 2.1
@@ -185,10 +175,6 @@ workflows:
requires:
- cargo_fetch
- clippy:
requires:
- cargo_fetch
# Linux Desktop 64bit
- test_x86_64-unknown-linux-gnu:
requires:

4
.gitignore vendored
View File

@@ -18,7 +18,3 @@ __pycache__
python/src/deltachat/capi*.so
python/liveconfig*
# ignore doxgen generated files
deltachat-ffi/html
deltachat-ffi/xml

688
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.4"
version = "1.0.0-alpha.3"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
@@ -10,13 +10,11 @@ cc = "1.0.35"
pkg-config = "0.3"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
pgp = { version = "0.2", default-features = false }
hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
smallvec = "0.6.9"
reqwest = "0.9.15"
num-derive = "0.2.5"
@@ -38,6 +36,7 @@ rustyline = "4.1.0"
lazy_static = "1.3.0"
regex = "1.1.6"
rusqlite = { version = "0.20", features = ["bundled"] }
addr = "0.2.0"
r2d2_sqlite = "0.12.0"
r2d2 = "0.8.5"
strum = "0.15.0"
@@ -46,10 +45,6 @@ thread-local-object = "0.1.0"
backtrace = "0.3.33"
byteorder = "1.3.1"
itertools = "0.8.0"
image-meta = "0.1.0"
quick-xml = "0.15.0"
escaper = "0.1.0"
bitflags = "1.1.0"
[dev-dependencies]
tempfile = "3.0"
@@ -58,8 +53,7 @@ pretty_env_logger = "0.3.0"
[workspace]
members = [
"deltachat-ffi",
"deltachat_derive",
"deltachat-ffi"
]
[[example]]

View File

@@ -89,14 +89,6 @@ $ cargo test --all
$ cargo build -p deltachat_ffi --release
```
### Expensive tests
Some tests are expensive and marked with `#[ignore]`, to run these
use the `--ignored` argument to the test binary (not to cargo itself):
```sh
$ cargo test -- --ignored
```
## Features
- `vendored`: When using Openssl for TLS, this bundles a vendored version.

View File

@@ -1,6 +0,0 @@
[dependencies.std]
features = ["panic-unwind"]
# if using `cargo test`
[dependencies.test]
stage = 1

View File

@@ -16,7 +16,7 @@ export BRANCH=${CIRCLE_BRANCH:-test7}
#fi
# run everything else inside docker (TESTS, DOCS, WHEELS)
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
docker run -e BRANCH -e TESTS -e DOCS \
--rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh

View File

@@ -15,7 +15,6 @@ export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# python docs to py.delta.chat
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
"$PYDOCDIR/html/" \
@@ -34,18 +33,15 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip3 install -U setuptools
pip3 install devpi-client
pip install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN
N_BRANCH=${BRANCH//[\/]}
devpi use dc/$N_BRANCH || {
devpi index -c $N_BRANCH
devpi use dc/$N_BRANCH
devpi use dc/$BRANCH || {
devpi index -c $BRANCH
devpi use dc/$BRANCH
}
devpi index $N_BRANCH bases=/root/pypi
devpi index $BRANCH bases=/root/pypi
devpi upload deltachat*.whl
popd

View File

@@ -3,9 +3,9 @@
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-04-19 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/
rm -rf /root/.rustup/toolchains/nightly-2019-04-19-x86_64-unknown-linux-gnu/share/

View File

@@ -37,10 +37,6 @@ if [ -n "$TESTS" ]; then
export PYTHONDONTWRITEBYTECODE=1
# 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 lint,py27,py35,py36,py37,auditwheels
popd
fi

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-alpha.4"
version = "1.0.0-alpha.3"
description = "Deltachat FFI"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
@@ -25,3 +25,4 @@ default = ["vendored", "nightly", "ringbuf"]
vendored = ["deltachat/vendored"]
nightly = ["deltachat/nightly"]
ringbuf = ["deltachat/ringbuf"]

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,7 +0,0 @@
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
div.fragment {
background-color: #e0e0e0;
border: 0;
padding: 1em;
}

View File

@@ -1,10 +1 @@
# Delta Chat C Interface
## Documentation
To generate the C Interface documentation,
call doxygen in the `deltachat-ffi` directory
and browse the `html` subdirectory.
If thinks work,
the documentation is also available online at <https://c.delta.chat>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +0,0 @@
[package]
name = "deltachat_derive"
version = "0.1.0"
authors = ["Dmitry Bogatov <KAction@debian.org>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
syn = "0.14.4"
quote = "0.6.3"

View File

@@ -1,44 +0,0 @@
#![recursion_limit = "128"]
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;
// For now, assume (not check) that these macroses are applied to enum without
// data. If this assumption is violated, compiler error will point to
// generated code, which is not very user-friendly.
#[proc_macro_derive(ToSql)]
pub fn to_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl rusqlite::types::ToSql for #name {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let num = *self as i64;
let value = rusqlite::types::Value::Integer(num);
let output = rusqlite::types::ToSqlOutput::Owned(value);
std::result::Result::Ok(output)
}
}
};
gen.into()
}
#[proc_macro_derive(FromSql)]
pub fn from_sql_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl rusqlite::types::FromSql for #name {
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
let inner = rusqlite::types::FromSql::column_result(col)?;
num_traits::FromPrimitive::from_i64(inner).ok_or(rusqlite::types::FromSqlError::InvalidType)
}
}
};
gen.into()
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
//!
//! Usage: cargo run --example repl --release -- <databasefile>
//! All further options can be set using the set-command (type ? for help).
#![feature(ptr_cast)]
#[macro_use]
extern crate deltachat;
@@ -14,17 +15,16 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
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_configure::*;
use deltachat::dc_job::*;
use deltachat::dc_securejoin::*;
use deltachat::dc_tools::*;
use deltachat::job::*;
use deltachat::oauth2::*;
use deltachat::types::*;
use deltachat::x::*;
@@ -173,11 +173,13 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_imap = std::thread::spawn(move || loop {
while_running!({
perform_imap_jobs(&ctx.read().unwrap());
perform_imap_fetch(&ctx.read().unwrap());
unsafe {
dc_perform_imap_jobs(&ctx.read().unwrap());
dc_perform_imap_fetch(&ctx.read().unwrap());
}
while_running!({
let context = ctx.read().unwrap();
perform_imap_idle(&context);
dc_perform_imap_idle(&context);
});
});
});
@@ -185,9 +187,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_mvbox = std::thread::spawn(move || loop {
while_running!({
perform_mvbox_fetch(&ctx.read().unwrap());
unsafe { dc_perform_mvbox_fetch(&ctx.read().unwrap()) };
while_running!({
perform_mvbox_idle(&ctx.read().unwrap());
unsafe { dc_perform_mvbox_idle(&ctx.read().unwrap()) };
});
});
});
@@ -195,9 +197,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c.clone();
let handle_sentbox = std::thread::spawn(move || loop {
while_running!({
perform_sentbox_fetch(&ctx.read().unwrap());
unsafe { dc_perform_sentbox_fetch(&ctx.read().unwrap()) };
while_running!({
perform_sentbox_idle(&ctx.read().unwrap());
unsafe { dc_perform_sentbox_idle(&ctx.read().unwrap()) };
});
});
});
@@ -205,9 +207,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
let ctx = c;
let handle_smtp = std::thread::spawn(move || loop {
while_running!({
perform_smtp_jobs(&ctx.read().unwrap());
unsafe { dc_perform_smtp_jobs(&ctx.read().unwrap()) };
while_running!({
perform_smtp_idle(&ctx.read().unwrap());
unsafe { dc_perform_smtp_idle(&ctx.read().unwrap()) };
});
});
});
@@ -225,10 +227,12 @@ fn stop_threads(context: &Context) {
println!("Stopping threads");
IS_RUNNING.store(false, Ordering::Relaxed);
interrupt_imap_idle(context);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
interrupt_smtp_idle(context);
unsafe {
dc_interrupt_imap_idle(context);
dc_interrupt_mvbox_idle(context);
dc_interrupt_sentbox_idle(context);
dc_interrupt_smtp_idle(context);
}
handle.handle_imap.take().unwrap().join().unwrap();
handle.handle_mvbox.take().unwrap().join().unwrap();
@@ -331,8 +335,8 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
"delcontact",
"cleanupcontacts",
];
const MISC_COMMANDS: [&'static str; 9] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
const MISC_COMMANDS: [&'static str; 8] = [
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "help",
];
impl Hinter for DcHelper {
@@ -385,7 +389,11 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
let mut context = dc_context_new(
Some(receive_event),
0 as *mut libc::c_void,
Some("CLI".into()),
);
unsafe { dc_cmdline_skip_auth() };
@@ -448,6 +456,12 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
println!("history saved");
{
stop_threads(&ctx.read().unwrap());
unsafe {
let mut ctx = ctx.write().unwrap();
dc_close(&mut ctx);
dc_context_unref(&mut ctx);
}
}
Ok(())
@@ -480,19 +494,19 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
if HANDLE.clone().lock().unwrap().is_some() {
println!("smtp-jobs are already running in a thread.",);
} else {
perform_smtp_jobs(&ctx.read().unwrap());
dc_perform_smtp_jobs(&ctx.read().unwrap());
}
}
"imap-jobs" => {
if HANDLE.clone().lock().unwrap().is_some() {
println!("imap-jobs are already running in a thread.");
} else {
perform_imap_jobs(&ctx.read().unwrap());
dc_perform_imap_jobs(&ctx.read().unwrap());
}
}
"configure" => {
start_threads(ctx.clone());
configure(&ctx.read().unwrap());
dc_configure(&ctx.read().unwrap());
}
"oauth2" => {
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
@@ -542,7 +556,7 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
}
}
"exit" | "quit" => return Ok(ExitResult::Exit),
"exit" => return Ok(ExitResult::Exit),
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
}

View File

@@ -5,16 +5,18 @@ use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
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::dc_chat::*;
use deltachat::dc_configure::*;
use deltachat::dc_job::{
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
dc_perform_smtp_jobs,
};
use deltachat::dc_lot::*;
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
@@ -51,12 +53,12 @@ fn main() {
let r1 = running.clone();
let t1 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_imap_jobs(&ctx1);
dc_perform_imap_jobs(&ctx1);
if *r1.read().unwrap() {
perform_imap_fetch(&ctx1);
dc_perform_imap_fetch(&ctx1);
if *r1.read().unwrap() {
perform_imap_idle(&ctx1);
dc_perform_imap_idle(&ctx1);
}
}
}
@@ -66,9 +68,9 @@ fn main() {
let r1 = running.clone();
let t2 = thread::spawn(move || {
while *r1.read().unwrap() {
perform_smtp_jobs(&ctx1);
dc_perform_smtp_jobs(&ctx1);
if *r1.read().unwrap() {
perform_smtp_idle(&ctx1);
dc_perform_smtp_idle(&ctx1);
}
}
});
@@ -87,24 +89,36 @@ fn main() {
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
.unwrap();
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
configure(&ctx);
dc_configure(&ctx);
thread::sleep(duration);
println!("sending a message");
let contact_id =
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
dc_send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into());
println!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
for i in 0..chats.len() {
let summary = chats.get_summary(0, None);
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
let summary = chats.get_summary(0, std::ptr::null_mut());
let text1 = dc_lot_get_text1(summary);
let text2 = dc_lot_get_text2(summary);
let text1_s = if !text1.is_null() {
Some(CStr::from_ptr(text1))
} else {
None
};
let text2_s = if !text2.is_null() {
Some(CStr::from_ptr(text2))
} else {
None
};
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
dc_lot_unref(summary);
}
thread::sleep(duration);
@@ -122,8 +136,8 @@ fn main() {
println!("stopping threads");
*running.clone().write().unwrap() = false;
deltachat::job::interrupt_imap_idle(&ctx);
deltachat::job::interrupt_smtp_idle(&ctx);
deltachat::dc_job::dc_interrupt_imap_idle(&ctx);
deltachat::dc_job::dc_interrupt_smtp_idle(&ctx);
println!("joining");
t1.join().unwrap();

View File

@@ -15,7 +15,7 @@ without any "build-from-source" steps.
1. `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
then create a fresh python environment and activate it in your shell::
virtualenv venv # or: python -m venv
virtualenv -p python3 venv
source venv/bin/activate
Afterwards, invoking ``python`` or ``pip install`` will only
@@ -39,12 +39,6 @@ and push them to a python package index. To install the latest github ``master``
pip install -i https://m.devpi.net/dc/master deltachat
.. note::
If you can help to automate the building of wheels for Mac or Windows,
that'd be much appreciated! please then get
`in contact with us <https://delta.chat/en/contribute>`_.
Installing bindings from source
===============================
@@ -54,55 +48,34 @@ to core deltachat library::
git clone https://github.com/deltachat/deltachat-core-rust
cd deltachat-core-rust
cargo build -p deltachat_ffi --release
This will result in a ``libdeltachat.so`` and ``libdeltachat.a`` files
in the ``target/release`` directory. These files are needed for
creating the python bindings for deltachat::
cd python
DCC_RS_DEV=`pwd`/.. pip install -e .
If you don't have one active, create and activate a python "virtualenv":
Now test if the bindings find the correct library::
python virtualenv venv # or python -m venv
source venv/bin/activate
python -c 'import deltachat ; print(deltachat.__version__)'
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 requests
then cargo-build and install the deltachat bindings::
python install_python_bindings.py
The bindings will be installed in release mode but with debug symbols.
The release mode is neccessary because some tests generate RSA keys
which is prohibitively slow in debug mode.
After succcessul binding installation you can finally run the tests::
pytest -v tests
This should print your deltachat bindings version.
.. note::
Some tests are sometimes failing/hanging because of
https://github.com/deltachat/deltachat-core-rust/issues/331
and
https://github.com/deltachat/deltachat-core-rust/issues/326
If you can help to automate the building of wheels for Mac or Windows,
that'd be much appreciated! please then get
`in contact with us <https://delta.chat/en/contribute>`_.
Using a system-installed deltachat-core-rust
--------------------------------------------
running "live" tests (experimental)
-----------------------------------
If you want to run "liveconfig" functional tests you can set
``DCC_PY_LIVECONFIG`` to:
- a particular https-url that you can ask for from the delta
chat devs.
- or the path of a file that contains two lines, each describing
via "addr=... mail_pwd=..." a test account login that will
be used for the live tests.
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
e-mail accounts and run through all functional "liveconfig" tests.
When calling ``pip`` without specifying the ``DCC_RS_DEV`` environment
variable cffi will try to use a ``deltachat.h`` from a system location
like ``/usr/local/include`` and will try to dynamically link against a
``libdeltachat.so`` in a similar location (e.g. ``/usr/local/lib``).
Code examples
@@ -111,34 +84,68 @@ Code examples
You may look at `examples <https://py.delta.chat/examples.html>`_.
Running tests
=============
Get a checkout of the `deltachat-core-rust github repository`_ and type::
pip install tox
./run-integration-tests.sh
If you want to run functional tests with real
e-mail test accounts, generate a "liveconfig" file where each
lines contains test account settings, for example::
# 'liveconfig' file specifying imap/smtp accounts
addr=some-email@example.org mail_pw=password
addr=other-email@example.org mail_pw=otherpassword
The "keyword=value" style allows to specify any
`deltachat account config setting <https://c.delta.chat/classdc__context__t.html#aff3b894f6cfca46cab5248fdffdf083d>`_ so you can also specify smtp or imap servers, ports, ssl modes etc.
Typically DC's automatic configuration allows to not specify these settings.
The ``run-integration-tests.sh`` script will automatically use
``python/liveconfig`` if it exists, to manually run tests with this
``liveconfig`` file use::
tox -- --liveconfig liveconfig
.. _`deltachat-core-rust github repository`: https://github.com/deltachat/deltachat-core-rust
.. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust
Running test using a debug build
--------------------------------
If you need to examine e.g. a coredump you may want to run the tests
using a debug build::
DCC_RS_TARGET=debug ./run-integration-tests.sh -e py37 -- -x -v -k failing_test
Building manylinux1 wheels
==========================
.. note::
This section may not fully work.
Building portable manylinux1 wheels which come with libdeltachat.so
and all it's dependencies is easy using the provided docker tooling.
using docker pull / premade images
------------------------------------
We publish a build environment under the ``deltachat/coredeps`` tag so
We publish a build environment under the ``deltachat/wheel`` tag so
that you can pull it from the ``hub.docker.com`` site's "deltachat"
organization::
$ docker pull deltachat/coredeps
$ docker pull deltachat/wheel
This docker image can be used to run tests and build Python wheels for all interpreters::
The ``deltachat/wheel`` image can be used to build both libdeltachat.so
and the Python wheels::
$ bash ci_scripts/ci_run.sh
$ docker run --rm -it -v $(pwd):/io/ deltachat/wheel /io/python/wheelbuilder/build-wheels.sh
This command runs tests and build-wheel scripts in a docker container.
This command runs a script within the image, after mounting ``$(pwd)`` as ``/io`` within
the docker image. The script is specified as a path within the docker image's filesystem.
The resulting wheel files will be in ``python/wheelhouse``.
Optionally build your own docker image
@@ -147,10 +154,10 @@ Optionally build your own docker image
If you want to build your own custom docker image you can do this::
$ cd deltachat-core # cd to deltachat-core checkout directory
$ docker build -t deltachat/coredeps ci_scripts/docker_coredeps
$ docker build -t deltachat/wheel python/wheelbuilder/
This will use the ``ci_scripts/docker_coredeps/Dockerfile`` to build
up docker image called ``deltachat/coredeps``. You can afterwards
This will use the ``python/wheelbuilder/Dockerfile`` to build
up docker image called ``deltachat/wheel``. You can afterwards
find it with::
$ docker images

View File

@@ -37,3 +37,16 @@ Message
.. autoclass:: deltachat.message.Message
:members:
MessageType
------------
.. autoclass:: deltachat.message.MessageType
:members:
MessageState
------------
.. autoclass:: deltachat.message.MessageState
:members:

View File

@@ -8,7 +8,8 @@ Playing around on the commandline
----------------------------------
Once you have :doc:`installed deltachat bindings <install>`
you can start playing from the python interpreter commandline.
you can start playing from the python interpreter commandline::
For example you can type ``python`` and then::
# instantiate and configure deltachat account
@@ -22,7 +23,7 @@ For example you can type ``python`` and then::
# create a contact and send a message
contact = ac.create_contact("someother@email.address")
chat = ac.create_chat_by_contact(contact)
chat.send_text("hi from the python interpreter command line")
chat.send_text_message("hi from the python interpreter command line")
Checkout our :doc:`api` for the various high-level things you can do
to send/receive messages, create contacts and chats.

View File

@@ -6,18 +6,29 @@
import os
import subprocess
import os
if __name__ == "__main__":
os.environ["DCC_RS_TARGET"] = target = "release"
if "DCC_RS_DEV" not in os.environ:
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ["DCC_RS_DEV"] = dn
os.environ["RUSTFLAGS"] = "-g"
subprocess.check_call([
"cargo", "build", "-p", "deltachat_ffi", "--" + target
])
toml = os.path.join(os.getcwd(), "..", "Cargo.toml")
assert os.path.exists(toml)
with open(toml) as f:
s = orig = f.read()
s += "\n"
s += "[profile.release]\n"
s += "debug = true\n"
with open(toml, "w") as f:
f.write(s)
print("temporarily modifying Cargo.toml to provide release build with debug symbols ")
try:
subprocess.check_call([
"cargo", "build", "-p", "deltachat_ffi", "--" + target
])
finally:
with open(toml, "w") as f:
f.write(orig)
print("\nreseted Cargo.toml to previous original state")
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
subprocess.check_call([

View File

@@ -34,14 +34,13 @@ def py_dc_callback(ctx, evt, data1, data2):
if data1 and event_sig_types & 1:
data1 = ffi.string(ffi.cast('char*', data1)).decode("utf8")
if data2 and event_sig_types & 2:
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
try:
if isinstance(data2, bytes):
data2 = data2.decode("utf8")
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
except UnicodeDecodeError:
# XXX ignoring the decode error is not quite correct but for now
# XXX ignoring this error is not quite correct but for now
# i don't want to hunt down encoding problems in the c lib
pass
data2 = ffi.string(ffi.cast('char*', data2))
try:
ret = callback(ctx, evt_name, data1, data2)
if ret is None:

View File

@@ -6,15 +6,10 @@ import platform
import os
import cffi
import shutil
from os.path import dirname as dn
from os.path import abspath
def ffibuilder():
projdir = os.environ.get('DCC_RS_DEV')
if not projdir:
p = dn(dn(dn(dn(abspath(__file__)))))
projdir = os.environ["DCC_RS_DEV"] = p
target = os.environ.get('DCC_RS_TARGET', 'release')
if projdir:
if platform.system() == 'Darwin':

View File

@@ -50,9 +50,8 @@ class Account(object):
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
# XXX this can cause "illegal instructions" at test ends so we omit it for now
# def __del__(self):
# self.shutdown()
def __del__(self):
self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -176,7 +175,7 @@ class Account(object):
whose name or e-mail matches query.
:param only_verified: if true only return verified contacts.
:param with_self: if true the self-contact is also returned.
:returns: list of :class:`deltachat.chatting.Contact` objects.
:returns: list of :class:`deltachat.message.Message` objects.
"""
flags = 0
query = as_dc_charpointer(query)

View File

@@ -16,4 +16,4 @@ def iter_array(dc_array_t, constructor):
def from_dc_charpointer(obj):
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8")
return ffi.string(obj).decode("utf8")

View File

@@ -142,7 +142,7 @@ class Message(object):
import email.parser
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id)
if mime_headers:
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
s = ffi.string(mime_headers)
if isinstance(s, bytes):
s = s.decode("ascii")
return email.message_from_string(s)

View File

@@ -1,9 +1,9 @@
from __future__ import print_function
import os
import pytest
import requests
import time
from deltachat import Account
from deltachat import props
from deltachat.capi import lib
import tempfile
@@ -36,8 +36,6 @@ def pytest_runtest_call(item):
def pytest_report_header(config, startdir):
summary = []
t = tempfile.mktemp()
try:
ac = Account(t, eventlogging=False)
@@ -45,18 +43,13 @@ def pytest_report_header(config, startdir):
ac.shutdown()
finally:
os.remove(t)
summary.extend(['Deltachat core={} sqlite={}'.format(
summary = ['Deltachat core={} sqlite={}'.format(
info['deltachat_core_version'],
info['sqlite_version'],
)])
cfg = config.option.liveconfig
)]
cfg = config.getoption('--liveconfig')
if cfg:
if "#" in cfg:
url, token = cfg.split("#", 1)
summary.append('Liveconfig provider: {}#<token ommitted>'.format(url))
else:
summary.append('Liveconfig file: {}'.format(cfg))
summary.append('Liveconfig: {}'.format(os.path.abspath(cfg)))
return summary
@@ -73,56 +66,9 @@ def data():
return Data()
class SessionLiveConfigFromFile:
def __init__(self, fn):
self.fn = fn
self.configlist = []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
self.configlist.append(d)
def get(self, index):
return self.configlist[index]
def exists(self):
return bool(self.configlist)
class SessionLiveConfigFromURL:
def __init__(self, url, create_token):
self.configlist = []
for i in range(2):
res = requests.post(url, json={"token_create_user": int(create_token)})
if res.status_code != 200:
pytest.skip("creating newtmpuser failed {!r}".format(res))
d = res.json()
config = dict(addr=d["email"], mail_pw=d["password"])
self.configlist.append(config)
def get(self, index):
return self.configlist[index]
def exists(self):
return bool(self.configlist)
@pytest.fixture(scope="session")
def session_liveconfig(request):
liveconfig_opt = request.config.option.liveconfig
if liveconfig_opt:
if liveconfig_opt.startswith("http"):
url, create_token = liveconfig_opt.split("#", 1)
return SessionLiveConfigFromURL(url, create_token)
else:
return SessionLiveConfigFromFile(liveconfig_opt)
@pytest.fixture
def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
def acfactory(pytestconfig, tmpdir, request):
fn = pytestconfig.getoption("--liveconfig")
class AccountMaker:
def __init__(self):
@@ -136,6 +82,18 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
fin = self._finalizers.pop()
fin()
@props.cached
def configlist(self):
configlist = []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
d = {}
for part in line.split():
name, value = part.split("=")
d[name] = value
configlist.append(d)
return configlist
def get_unconfigured_account(self):
self.offline_count += 1
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
@@ -158,12 +116,10 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
return ac
def get_online_configuring_account(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
configdict = session_liveconfig.get(self.live_count)
if not fn:
pytest.skip("specify a --liveconfig file to run tests with real accounts")
self.live_count += 1
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
configdict = self.configlist.pop(0)
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time

View File

@@ -427,37 +427,6 @@ class TestOnlineAccount:
lp.step("2")
assert msg_out.is_out_mdn_received()
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
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")
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.text == "message1"
lp.sec("create new chat with contact and send back (encrypted) message")
chat2b = ac2.create_chat_by_message(msg_in)
chat2b.send_text("message-back")
lp.sec("wait for ac1 to receive message")
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
assert ev[1] == chat.id
assert ev[2] > msg_out.id
msg_back = ac1.get_message_by_id(ev[2])
assert msg_back.text == "message-back"
def test_saved_mime_on_received_message(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()

View File

@@ -19,7 +19,6 @@ deps =
pytest
pytest-faulthandler
pdbpp
requests
[testenv:auditwheels]
skipsdist = True
@@ -52,7 +51,6 @@ commands =
[pytest]
addopts = -v -rs
python_files = tests/test_*.py
norecursedirs = .tox
xfail_strict=true

View File

@@ -23,7 +23,7 @@ if [ $? != 0 ]; then
fi
pushd python
if [ -e "./liveconfig" && -z "$DCC_PY_LIVECONFIG" ]; then
if [ -e "./liveconfig" ]; then
export DCC_PY_LIVECONFIG=liveconfig
fi
tox "$@"

View File

@@ -1 +1 @@
nightly-2019-08-13
nightly-2019-07-10

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
use crate::dc_chat::*;
use crate::dc_lot::*;
use crate::dc_msg::*;
use crate::dc_tools::*;
use crate::error::Result;
use crate::lot::Lot;
use crate::message::*;
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
@@ -195,9 +196,9 @@ impl<'a> Chatlist<'a> {
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
ids.push((DC_CHAT_ID_ALLDONE_HINT as u32, 0));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
ids.push((DC_CHAT_ID_ARCHIVED_LINK as u32, 0));
}
Ok(Chatlist { context, ids })
@@ -247,63 +248,57 @@ impl<'a> Chatlist<'a> {
/// - 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, index: usize, chat: Option<&Chat<'a>>) -> Lot {
pub unsafe fn get_summary(&self, index: usize, mut chat: *mut Chat<'a>) -> *mut dc_lot_t {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
// Also, sth. as "No messages" would not work if the summary comes from a message.
let mut ret = Lot::new();
let mut ret = dc_lot_new();
if index >= self.ids.len() {
ret.text2 = Some("ErrBadChatlistIndex".to_string());
(*ret).text2 = "ErrBadChatlistIndex".strdup();
return ret;
}
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else {
if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) {
chat_loaded = chat;
&chat_loaded
} else {
return ret;
}
};
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let lastmsg = if 0 != 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(self.context, lastmsg.from_id).ok();
}
if chat.is_null() {
chat = dc_chat_new(self.context);
let chat_to_delete = chat;
if !dc_chat_load_from_db(chat, self.ids[index].0) {
(*ret).text2 = "ErrCannotReadChat".strdup();
dc_chat_unref(chat_to_delete);
Some(lastmsg)
} else {
None
return ret;
}
}
let lastmsg = if 0 != lastmsg_id {
let lastmsg = dc_msg_new_untyped(self.context);
dc_msg_load_from_db(lastmsg, self.context, lastmsg_id);
if (*lastmsg).from_id != 1 as libc::c_uint
&& ((*chat).type_0 == DC_CHAT_TYPE_GROUP
|| (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP)
{
lastcontact = Contact::load_from_db(self.context, (*lastmsg).from_id).ok();
}
lastmsg
} else {
None
std::ptr::null_mut()
};
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
{
ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string());
if (*chat).id == DC_CHAT_ID_ARCHIVED_LINK as u32 {
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
} else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_UNDEFINED as u32 {
(*ret).text2 = self.context.stock_str(StockMessage::NoMessages).strdup();
} else {
ret.fill(
&mut lastmsg.unwrap(),
chat,
lastcontact.as_ref(),
self.context,
);
dc_lot_fill(ret, lastmsg, chat, lastcontact.as_ref(), self.context);
}
dc_msg_unref(lastmsg);
ret
}
}

View File

@@ -3,9 +3,9 @@ use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::constants::DC_VERSION_STR;
use crate::context::Context;
use crate::dc_job::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::job::*;
use crate::stock::StockMessage;
/// The available configuration keys.
@@ -48,14 +48,11 @@ pub enum Config {
ConfiguredMailUser,
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredMailSecurity,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
Configured,
// Deprecated
#[strum(serialize = "sys.version")]
@@ -74,7 +71,7 @@ impl Context {
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysVersion => Some(std::str::from_utf8(DC_VERSION_STR).unwrap().into()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_config(self, key),
@@ -102,17 +99,17 @@ impl Context {
}
Config::InboxWatch => {
let ret = self.sql.set_config(self, key, value);
interrupt_imap_idle(self);
unsafe { dc_interrupt_imap_idle(self) };
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_config(self, key, value);
interrupt_sentbox_idle(self);
unsafe { dc_interrupt_sentbox_idle(self) };
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_config(self, key, value);
interrupt_mvbox_idle(self);
unsafe { dc_interrupt_mvbox_idle(self) };
ret
}
Config::Selfstatus => {
@@ -123,7 +120,8 @@ impl Context {
value
};
self.sql.set_config(self, key, val)
let ret = self.sql.set_config(self, key, val);
ret
}
_ => self.sql.set_config(self, key, value),
}

View File

@@ -1,224 +0,0 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::x::*;
use super::read_autoconf_file;
/* ******************************************************************************
* Thunderbird's Autoconfigure
******************************************************************************/
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
#[repr(C)]
struct moz_autoconfigure_t<'a> {
pub in_0: &'a dc_loginparam_t,
pub in_emaildomain: *mut libc::c_char,
pub in_emaillocalpart: *mut libc::c_char,
pub out: dc_loginparam_t,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_server: libc::c_int,
pub tag_config: libc::c_int,
}
pub unsafe fn moz_autoconfigure(
context: &Context,
url: &str,
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
let mut moz_ac = moz_autoconfigure_t {
in_0: param_in,
in_emaildomain: std::ptr::null_mut(),
in_emaillocalpart: std::ptr::null_mut(),
out: dc_loginparam_new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_server: 0,
tag_config: 0,
};
let url_c = url.strdup();
let xml_raw = read_autoconf_file(context, url_c);
free(url_c as *mut libc::c_void);
if xml_raw.is_null() {
return None;
}
moz_ac.in_emaillocalpart = param_in.addr.strdup();
let p = strchr(moz_ac.in_emaillocalpart, '@' as i32);
if p.is_null() {
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
return None;
}
*p = 0 as libc::c_char;
moz_ac.in_emaildomain = dc_strdup(p.offset(1isize));
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
Ok(quick_xml::events::Event::Text(ref e)) => {
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
}
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
if moz_ac.out.mail_server.is_empty()
|| moz_ac.out.mail_port == 0
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
{
let r = dc_loginparam_get_readable(&moz_ac.out);
warn!(context, 0, "Bad or incomplete autoconfig: {}", r,);
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
return None;
}
free(xml_raw as *mut libc::c_void);
free(moz_ac.in_emaildomain as *mut libc::c_void);
free(moz_ac.in_emaillocalpart as *mut libc::c_void);
Some(moz_ac.out)
}
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
event: &BytesText,
moz_ac: &mut moz_autoconfigure_t,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let addr = &moz_ac.in_0.addr;
let email_local = as_str(moz_ac.in_emaillocalpart);
let email_domain = as_str(moz_ac.in_emaildomain);
let val = val
.trim()
.replace("%EMAILADDRESS%", addr)
.replace("%EMAILLOCALPART%", email_local)
.replace("%EMAILDOMAIN%", email_domain);
if moz_ac.tag_server == 1 {
match moz_ac.tag_config {
10 => moz_ac.out.mail_server = val,
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
12 => moz_ac.out.mail_user = val,
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= 0x200
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= 0x100
}
if val_lower == "plain" {
moz_ac.out.server_flags |= 0x400
}
}
_ => {}
}
} else if moz_ac.tag_server == 2 {
match moz_ac.tag_config {
10 => moz_ac.out.send_server = val,
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
12 => moz_ac.out.send_user = val,
13 => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= 0x20000
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= 0x10000
}
if val_lower == "plain" {
moz_ac.out.server_flags |= 0x40000
}
}
_ => {}
}
}
}
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_imap_set = 1;
} else if tag == "outgoingserver" {
moz_ac.tag_server = 0;
moz_ac.tag_config = 0;
moz_ac.out_smtp_set = 1;
} else {
moz_ac.tag_config = 0;
}
}
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
moz_ac: &mut moz_autoconfigure_t,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "incomingserver" {
moz_ac.tag_server = if let Some(typ) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
.unwrap_or_default()
}) {
let typ = typ
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.to_lowercase();
if typ == "imap" && moz_ac.out_imap_set == 0 {
1
} else {
0
}
} else {
0
};
moz_ac.tag_config = 0;
} else if tag == "outgoingserver" {
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
moz_ac.tag_config = 0;
} else if tag == "hostname" {
moz_ac.tag_config = 10;
} else if tag == "port" {
moz_ac.tag_config = 11;
} else if tag == "sockettype" {
moz_ac.tag_config = 13;
} else if tag == "username" {
moz_ac.tag_config = 12;
}
}

View File

@@ -1,213 +0,0 @@
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::x::*;
use std::ptr;
use super::read_autoconf_file;
/* ******************************************************************************
* Outlook's Autodiscover
******************************************************************************/
#[repr(C)]
struct outlk_autodiscover_t<'a> {
pub in_0: &'a dc_loginparam_t,
pub out: dc_loginparam_t,
pub out_imap_set: libc::c_int,
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
pub config: [*mut libc::c_char; 6],
pub redirect: *mut libc::c_char,
}
pub unsafe fn outlk_autodiscover(
context: &Context,
url__: &str,
param_in: &dc_loginparam_t,
) -> Option<dc_loginparam_t> {
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
let mut url = url__.strdup();
let mut outlk_ad = outlk_autodiscover_t {
in_0: param_in,
out: dc_loginparam_new(),
out_imap_set: 0,
out_smtp_set: 0,
tag_config: 0,
config: [ptr::null_mut(); 6],
redirect: ptr::null_mut(),
};
let ok_to_continue;
let mut i = 0;
loop {
if !(i < 10) {
ok_to_continue = true;
break;
}
memset(
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
0,
::std::mem::size_of::<outlk_autodiscover_t>(),
);
xml_raw = read_autoconf_file(context, url);
if xml_raw.is_null() {
ok_to_continue = false;
break;
}
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
reader.trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
}
Ok(quick_xml::events::Event::End(ref e)) => {
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
}
Ok(quick_xml::events::Event::Text(ref e)) => {
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
}
Err(e) => {
error!(
context,
0,
"Configure xml: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
if !(!outlk_ad.config[5].is_null()
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
{
ok_to_continue = true;
break;
}
free(url as *mut libc::c_void);
url = dc_strdup(outlk_ad.config[5usize]);
outlk_clean_config(&mut outlk_ad);
free(xml_raw as *mut libc::c_void);
xml_raw = ptr::null_mut();
i += 1;
}
if ok_to_continue {
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
{
let r = dc_loginparam_get_readable(&outlk_ad.out);
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);
return None;
}
}
free(url as *mut libc::c_void);
free(xml_raw as *mut libc::c_void);
outlk_clean_config(&mut outlk_ad);
Some(outlk_ad.out)
}
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
for i in 0..6 {
free((*outlk_ad).config[i] as *mut libc::c_void);
(*outlk_ad).config[i] = ptr::null_mut();
}
}
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
event: &BytesText,
outlk_ad: &mut outlk_autodiscover_t,
reader: &quick_xml::Reader<B>,
) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
unsafe {
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
}
}
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
if !outlk_ad.config[1].is_null() {
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
let ssl_on = (!outlk_ad.config[4].is_null()
&& strcasecmp(
outlk_ad.config[4],
b"on\x00" as *const u8 as *const libc::c_char,
) == 0) as libc::c_int;
let ssl_off = (!outlk_ad.config[4].is_null()
&& strcasecmp(
outlk_ad.config[4],
b"off\x00" as *const u8 as *const libc::c_char,
) == 0) as libc::c_int;
if strcasecmp(
outlk_ad.config[1],
b"imap\x00" as *const u8 as *const libc::c_char,
) == 0
&& outlk_ad.out_imap_set == 0
{
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
outlk_ad.out.mail_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= 0x200
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x400
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
outlk_ad.config[1usize],
b"smtp\x00" as *const u8 as *const libc::c_char,
) == 0
&& outlk_ad.out_smtp_set == 0
{
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
outlk_ad.out.send_port = port;
if 0 != ssl_on {
outlk_ad.out.server_flags |= 0x20000
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x40000
}
outlk_ad.out_smtp_set = 1
}
}
outlk_clean_config(outlk_ad);
}
outlk_ad.tag_config = 0;
}
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "protocol" {
unsafe { outlk_clean_config(outlk_ad) };
} else if tag == "type" {
outlk_ad.tag_config = 1
} else if tag == "server" {
outlk_ad.tag_config = 2
} else if tag == "port" {
outlk_ad.tag_config = 3
} else if tag == "ssl" {
outlk_ad.tag_config = 4
} else if tag == "redirecturl" {
outlk_ad.tag_config = 5
};
}

View File

@@ -1,712 +0,0 @@
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::constants::Event;
use crate::context::Context;
use crate::dc_e2ee::*;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::imap::*;
use crate::job::*;
use crate::oauth2::*;
use crate::param::Params;
use crate::types::*;
mod auto_outlook;
use auto_outlook::outlk_autodiscover;
mod auto_mozilla;
use auto_mozilla::moz_autoconfigure;
macro_rules! progress {
($context:tt, $progress:expr) => {
assert!(
$progress >= 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb(
Event::CONFIGURE_PROGRESS,
$progress as uintptr_t,
0 as uintptr_t,
);
};
}
// connect
pub unsafe fn configure(context: &Context) {
if 0 != dc_has_ongoing(context) {
warn!(
context,
0, "There is already another ongoing process running.",
);
return;
}
job_kill_action(context, Action::ConfigureImap);
job_add(context, Action::ConfigureImap, 0, Params::new(), 0);
}
unsafe fn dc_has_ongoing(context: &Context) -> libc::c_int {
let s_a = context.running_state.clone();
let s = s_a.read().unwrap();
if s.ongoing_running || !s.shall_stop_ongoing {
1
} else {
0
}
}
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
}
}
pub fn dc_stop_ongoing_process(context: &Context) {
let s_a = context.running_state.clone();
let mut s = s_a.write().unwrap();
if s.ongoing_running && !s.shall_stop_ongoing {
info!(context, 0, "Signaling the ongoing process to stop ASAP.",);
s.shall_stop_ongoing = true;
} else {
info!(context, 0, "No ongoing process to stop.",);
};
}
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
#[allow(non_snake_case, unused_must_use)]
pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
let flags: libc::c_int;
let mut success = false;
let mut imap_connected_here = false;
let mut smtp_connected_here = false;
let mut ongoing_allocated_here = false;
let mut param_autoconfig = None;
if !(0 == dc_alloc_ongoing(context)) {
ongoing_allocated_here = true;
if !context.sql.is_open() {
error!(context, 0, "Cannot configure, database not opened.",);
} else {
context.inbox.read().unwrap().disconnect(context);
context
.sentbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context
.mvbox_thread
.read()
.unwrap()
.imap
.disconnect(context);
context.smtp.clone().lock().unwrap().disconnect();
info!(context, 0, "Configure ...",);
let s_a = context.running_state.clone();
let s = s_a.read().unwrap();
if !s.shall_stop_ongoing {
progress!(context, 1);
let mut param = dc_loginparam_read(context, &context.sql, "");
if param.addr.is_empty() {
error!(context, 0, "Please enter an email address.",);
} else {
let ok_to_continue0;
if 0 != param.server_flags & 0x2 {
// the used oauth2 addr may differ, check this.
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
// just use the given one.
if s.shall_stop_ongoing {
ok_to_continue0 = false;
} else {
progress!(context, 10);
if let Some(oauth2_addr) =
dc_get_oauth2_addr(context, &param.addr, &param.mail_pw)
.and_then(|e| e.parse().ok())
{
param.addr = oauth2_addr;
context
.sql
.set_config(context, "addr", Some(param.addr.as_str()))
.ok();
}
if s.shall_stop_ongoing {
ok_to_continue0 = false;
} else {
progress!(context, 20);
ok_to_continue0 = true;
}
}
} else {
ok_to_continue0 = true;
}
if ok_to_continue0 {
let mut ok_to_continue7 = false;
if let Ok(parsed) = param.addr.parse() {
let parsed: EmailAddress = parsed;
let param_domain = parsed.domain;
let param_addr_urlencoded =
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
if !s.shall_stop_ongoing {
progress!(context, 200);
/* 2. Autoconfig
**************************************************************************/
if param.mail_server.is_empty()
&& param.mail_port == 0
&& param.send_server.is_empty()
&& param.send_port == 0
&& param.send_user.is_empty()
&& param.server_flags & !0x2 == 0
{
let ok_to_continue1;
/*&&param->mail_user ==NULL -- the user can enter a loginname which is used by autoconfig then */
/*&&param->send_pw ==NULL -- the password cannot be auto-configured and is no criterion for autoconfig or not */
/* flags but OAuth2 avoid autoconfig */
let keep_flags = param.server_flags & 0x2;
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
if param_autoconfig.is_none() {
let url = format!(
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain,
param_addr_urlencoded
);
param_autoconfig = moz_autoconfigure(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue1 = false;
} else {
progress!(context, 300);
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = true;
}
if ok_to_continue1 {
let ok_to_continue2;
if param_autoconfig.is_none() {
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
let url = format!(
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
param_domain,
param_addr_urlencoded
);
param_autoconfig =
moz_autoconfigure(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue2 = false;
} else {
progress!(context, 310);
ok_to_continue2 = true;
}
} else {
ok_to_continue2 = true;
}
if ok_to_continue2 {
let mut i: libc::c_int = 0;
let ok_to_continue3;
loop {
if !(i <= 1) {
ok_to_continue3 = true;
break;
}
if param_autoconfig.is_none() {
/* Outlook uses always SSL but different domains */
let url = format!(
"https://{}{}/autodiscover/autodiscover.xml",
if i == 0 {
""
} else {
"autodiscover."
},
param_domain
);
param_autoconfig =
outlk_autodiscover(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue3 = false;
break;
}
progress!(context, 320 + i * 10);
}
i += 1
}
if ok_to_continue3 {
let ok_to_continue4;
if param_autoconfig.is_none() {
let url = format!(
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain,
param_addr_urlencoded
);
param_autoconfig =
moz_autoconfigure(context, &url, &param);
if s.shall_stop_ongoing {
ok_to_continue4 = false;
} else {
progress!(context, 340);
ok_to_continue4 = true;
}
} else {
ok_to_continue4 = true;
}
if ok_to_continue4 {
let ok_to_continue5;
if param_autoconfig.is_none() {
// do not transfer the email-address unencrypted
let url = format!(
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
param_autoconfig = moz_autoconfigure(
context, &url, &param,
);
if s.shall_stop_ongoing {
ok_to_continue5 = false;
} else {
progress!(context, 350);
ok_to_continue5 = true;
}
} else {
ok_to_continue5 = true;
}
if ok_to_continue5 {
let ok_to_continue6;
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
if param_autoconfig.is_none() {
/* always SSL for Thunderbird's database */
let url =
format!("https://autoconfig.thunderbird.net/v1.1/{}",
param_domain
);
param_autoconfig = moz_autoconfigure(
context, &url, &param,
);
if s.shall_stop_ongoing {
ok_to_continue6 = false;
} else {
progress!(context, 500);
ok_to_continue6 = true;
}
} else {
ok_to_continue6 = true;
}
if ok_to_continue6 {
if let Some(ref cfg) = param_autoconfig
{
let r =
dc_loginparam_get_readable(cfg);
info!(
context,
0, "Got autoconfig: {}", r
);
if !cfg.mail_user.is_empty() {
param.mail_user =
cfg.mail_user.clone();
}
param.mail_server =
cfg.mail_server.clone();
param.mail_port = cfg.mail_port;
param.send_server =
cfg.send_server.clone();
param.send_port = cfg.send_port;
param.send_user =
cfg.send_user.clone();
param.server_flags =
cfg.server_flags;
}
param.server_flags |= keep_flags;
ok_to_continue7 = true;
}
}
}
}
}
}
}
} else {
ok_to_continue7 = true;
}
if ok_to_continue7 {
if param.mail_server.is_empty() {
param.mail_server = format!("imap.{}", param_domain,)
}
if param.mail_port == 0 {
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
143
} else {
993
}
}
if param.mail_user.is_empty() {
param.mail_user = param.addr.clone();
}
if param.send_server.is_empty() && !param.mail_server.is_empty() {
param.send_server = param.mail_server.clone();
if param.send_server.starts_with("imap.") {
param.send_server =
param.send_server.replacen("imap", "smtp", 1);
}
}
if param.send_port == 0 {
param.send_port = if 0 != param.server_flags & 0x10000 {
587
} else if 0 != param.server_flags & 0x40000 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
}
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & (0x2 | 0x4)) {
param.server_flags &= !(0x2 | 0x4);
param.server_flags |= 0x4
}
if !dc_exactly_one_bit_set(
param.server_flags & (0x100 | 0x200 | 0x400),
) {
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |=
if param.send_port == 143 { 0x100 } else { 0x200 }
}
if !dc_exactly_one_bit_set(
param.server_flags & (0x10000 | 0x20000 | 0x40000),
) {
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= if param.send_port == 587 {
0x10000
} else if param.send_port == 25 {
0x40000
} else {
0x20000
}
}
/* do we have a complete configuration? */
if param.mail_server.is_empty()
|| param.mail_port == 0
|| param.mail_user.is_empty()
|| param.mail_pw.is_empty()
|| param.send_server.is_empty()
|| param.send_port == 0
|| param.send_user.is_empty()
|| param.send_pw.is_empty()
|| param.server_flags == 0
{
error!(context, 0, "Account settings incomplete.",);
} else if !s.shall_stop_ongoing {
progress!(context, 600);
/* try to connect to IMAP - if we did not got an autoconfig,
do some further tries with different settings and username variations */
let ok_to_continue8;
let mut username_variation = 0;
loop {
if !(username_variation <= 1) {
ok_to_continue8 = true;
break;
}
let r_0 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_0,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
break;
}
if !param_autoconfig.is_none() {
ok_to_continue8 = false;
break;
}
// probe STARTTLS/993
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 650 + username_variation * 30);
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= 0x100;
let r_1 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_1,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
break;
}
// probe STARTTLS/143
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 660 + username_variation * 30);
param.mail_port = 143;
let r_2 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_2,);
if context.inbox.read().unwrap().connect(context, &param) {
ok_to_continue8 = true;
break;
}
if 0 != username_variation {
ok_to_continue8 = false;
break;
}
// next probe round with only the localpart of the email-address as the loginname
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 670 + username_variation * 30);
param.server_flags &= !(0x100 | 0x200 | 0x400);
param.server_flags |= 0x200;
param.mail_port = 993;
if let Some(at) = param.mail_user.find('@') {
param.mail_user =
param.mail_user.split_at(at).0.to_string();
}
if let Some(at) = param.send_user.find('@') {
param.send_user =
param.send_user.split_at(at).0.to_string();
}
username_variation += 1
}
if ok_to_continue8 {
imap_connected_here = true;
if !s.shall_stop_ongoing {
progress!(context, 800);
let ok_to_continue9;
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
if !param_autoconfig.is_none() {
ok_to_continue9 = false;
} else if s.shall_stop_ongoing {
ok_to_continue9 = false;
} else {
progress!(context, 850);
param.server_flags &=
!(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 587;
let r_3 = dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_3,);
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
if s.shall_stop_ongoing {
ok_to_continue9 = false;
} else {
progress!(context, 860);
param.server_flags &=
!(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.send_port = 25;
let r_4 =
dc_loginparam_get_readable(&param);
info!(context, 0, "Trying: {}", r_4);
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
ok_to_continue9 = false;
} else {
ok_to_continue9 = true;
}
}
} else {
ok_to_continue9 = true;
}
}
} else {
ok_to_continue9 = true;
}
if ok_to_continue9 {
smtp_connected_here = true;
if !s.shall_stop_ongoing {
progress!(context, 900);
flags = if 0
!= context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1)
|| 0 != context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
{
0x1
} else {
0
};
context
.inbox
.read()
.unwrap()
.configure_folders(context, flags);
if !s.shall_stop_ongoing {
progress!(context, 910);
dc_loginparam_write(
context,
&param,
&context.sql,
"configured_",
);
context
.sql
.set_config_int(
context,
"configured",
1,
)
.ok();
if !s.shall_stop_ongoing {
progress!(context, 920);
dc_ensure_secret_key_exists(context);
success = true;
info!(
context,
0, "Configure completed."
);
if !s.shall_stop_ongoing {
progress!(context, 940);
}
}
}
}
}
}
}
}
}
} else {
error!(context, 0, "Bad email-address.");
}
}
}
}
}
}
if imap_connected_here {
context.inbox.read().unwrap().disconnect(context);
}
if smtp_connected_here {
context.smtp.clone().lock().unwrap().disconnect();
}
/*
if !success {
// disconnect if configure did not succeed
if imap_connected_here {
// context.inbox.read().unwrap().disconnect(context);
}
if smtp_connected_here {
// context.smtp.clone().lock().unwrap().disconnect();
}
} else {
assert!(imap_connected_here && smtp_connected_here);
info!(
context,
0, "Keeping IMAP/SMTP connections open after successful configuration"
);
}
*/
if ongoing_allocated_here {
dc_free_ongoing(context);
}
progress!(context, (if success { 1000 } else { 0 }));
}
pub unsafe fn dc_free_ongoing(context: &Context) {
let s_a = context.running_state.clone();
let mut s = s_a.write().unwrap();
s.ongoing_running = false;
s.shall_stop_ongoing = true;
}
pub unsafe fn dc_alloc_ongoing(context: &Context) -> libc::c_int {
if 0 != dc_has_ongoing(context) {
warn!(
context,
0, "There is already another ongoing process running.",
);
return 0;
}
let s_a = context.running_state.clone();
let mut s = s_a.write().unwrap();
s.ongoing_running = true;
s.shall_stop_ongoing = false;
1
}
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if imap.is_connected() {
ret_connected = 1
} else if context
.sql
.get_config_int(context, "configured")
.unwrap_or_default()
== 0
{
warn!(context, 0, "Not configured, cannot connect.",);
} else {
let param = dc_loginparam_read(context, &context.sql, "configured_");
// the trailing underscore is correct
if imap.connect(context, &param) {
ret_connected = 2;
}
}
ret_connected
}
pub fn read_autoconf_file(context: &Context, url: *const libc::c_char) -> *mut libc::c_char {
info!(context, 0, "Testing {} ...", to_string(url));
match reqwest::Client::new()
.get(as_str(url))
.send()
.and_then(|mut res| res.text())
{
Ok(res) => unsafe { res.strdup() },
Err(_err) => {
info!(context, 0, "Can\'t read file.",);
std::ptr::null_mut()
}
}
}

View File

@@ -1,124 +1,102 @@
//! Constants
#![allow(non_camel_case_types, dead_code)]
#![allow(non_camel_case_types)]
use num_traits::{FromPrimitive, ToPrimitive};
use rusqlite as sql;
use rusqlite::types::*;
use lazy_static::lazy_static;
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.3\x00";
use deltachat_derive::*;
lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum MoveState {
Undefined = 0,
Pending = 1,
Stay = 2,
Moving = 3,
}
// some defaults
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[repr(u8)]
pub enum Blocked {
Not = 0,
Manually = 1,
Deaddrop = 2,
}
impl Default for Blocked {
fn default() -> Self {
Blocked::Not
}
}
pub const DC_IMAP_SEEN: u32 = 0x1;
const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
pub const DC_MOVE_STATE_MOVING: u32 = 3;
pub const DC_MOVE_STATE_STAY: u32 = 2;
pub const DC_MOVE_STATE_PENDING: u32 = 1;
pub const DC_MOVE_STATE_UNDEFINED: u32 = 0;
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
const DC_GCM_ADDDAYMARKER: usize = 0x01;
pub const DC_GCM_ADDDAYMARKER: usize = 0x01;
const DC_GCL_VERIFIED_ONLY: usize = 0x01;
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
pub const DC_GCL_ADD_SELF: usize = 0x02;
/// param1 is a directory where the keys are written to
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
pub const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
/// param1 is a directory where the keys are searched in and read from
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
pub const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
/// param1 is a directory where the backup is written to
const DC_IMEX_EXPORT_BACKUP: usize = 11;
pub const DC_IMEX_EXPORT_BACKUP: usize = 11;
/// param1 is the file with the backup to import
const DC_IMEX_IMPORT_BACKUP: usize = 12;
pub const DC_IMEX_IMPORT_BACKUP: usize = 12;
/// id=contact
pub const DC_QR_ASK_VERIFYCONTACT: usize = 200;
/// text1=groupname
pub const DC_QR_ASK_VERIFYGROUP: usize = 202;
/// id=contact
pub const DC_QR_FPR_OK: usize = 210;
/// id=contact
pub const DC_QR_FPR_MISMATCH: usize = 220;
/// test1=formatted fingerprint
pub const DC_QR_FPR_WITHOUT_ADDR: usize = 230;
/// id=contact
pub const DC_QR_ADDR: usize = 320;
/// text1=text
pub const DC_QR_TEXT: usize = 330;
/// text1=URL
pub const DC_QR_URL: usize = 332;
/// text1=error string
pub const DC_QR_ERROR: usize = 400;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
pub const DC_CHAT_ID_DEADDROP: usize = 1;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
pub const DC_CHAT_ID_TRASH: u32 = 3;
pub const DC_CHAT_ID_TRASH: usize = 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;
pub const DC_CHAT_ID_MSGS_IN_CREATION: usize = 4;
/// virtual chat showing all messages flagged with msgs.starred=2
const DC_CHAT_ID_STARRED: u32 = 5;
pub const DC_CHAT_ID_STARRED: usize = 5;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
pub const DC_CHAT_ID_ARCHIVED_LINK: usize = 6;
/// only an indicator in a chatlist
pub const DC_CHAT_ID_ALLDONE_HINT: u32 = 7;
pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
/// larger chat IDs are "real" chats, their messages are "real" messages.
pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
#[derive(
Debug,
Display,
Clone,
Copy,
PartialEq,
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
IntoStaticStr,
)]
#[repr(u32)]
pub enum Chattype {
Undefined = 0,
Single = 100,
Group = 120,
VerifiedGroup = 130,
}
pub const DC_CHAT_TYPE_UNDEFINED: i32 = 0;
pub const DC_CHAT_TYPE_SINGLE: i32 = 100;
pub const DC_CHAT_TYPE_GROUP: i32 = 120;
pub const DC_CHAT_TYPE_VERIFIED_GROUP: i32 = 130;
impl Default for Chattype {
fn default() -> Self {
Chattype::Undefined
}
}
pub const DC_MSG_ID_MARKER1: usize = 1;
pub const DC_MSG_ID_DAYMARKER: usize = 9;
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
pub const DC_MSG_ID_MARKER1: u32 = 1;
const DC_MSG_ID_DAYMARKER: u32 = 9;
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
pub const DC_STATE_UNDEFINED: i32 = 0;
pub const DC_STATE_IN_FRESH: i32 = 10;
pub const DC_STATE_IN_NOTICED: i32 = 13;
pub const DC_STATE_IN_SEEN: i32 = 16;
pub const DC_STATE_OUT_PREPARING: i32 = 18;
pub const DC_STATE_OUT_DRAFT: i32 = 19;
pub const DC_STATE_OUT_PENDING: i32 = 20;
pub const DC_STATE_OUT_FAILED: i32 = 24;
/// to check if a mail was sent, use dc_msg_is_sent()
pub const DC_STATE_OUT_DELIVERED: i32 = 26;
pub const DC_STATE_OUT_MDN_RCVD: i32 = 28;
/// approx. max. length returned by dc_msg_get_text()
const DC_MAX_GET_TEXT_LEN: usize = 30000;
pub const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. length returned by dc_get_msg_info()
const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CONTACT_ID_UNDEFINED: usize = 0;
pub const DC_CONTACT_ID_SELF: usize = 1;
pub const DC_CONTACT_ID_DEVICE: usize = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
pub const DC_TEXT1_DRAFT: usize = 1;
pub const DC_TEXT1_USERNAME: usize = 2;
pub const DC_TEXT1_SELF: usize = 3;
pub const DC_CREATE_MVBOX: usize = 1;
@@ -128,13 +106,13 @@ pub const DC_CREATE_MVBOX: usize = 1;
// via dc_set_config() using the key "server_flags".
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
/// Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
const DC_LP_AUTH_NORMAL: usize = 0x4;
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -142,7 +120,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
@@ -154,22 +132,22 @@ pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
/// Connect to SMTP via SSL.
/// If this flag is set, automatic configuration is skipped.
const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// Connect to SMTP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is chosen
const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
/// if none of these flags are set, the default is chosen
const DC_LP_IMAP_SOCKET_FLAGS: usize =
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
/// if none of these flags are set, the default is chosen
const DC_LP_SMTP_SOCKET_FLAGS: usize =
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(i32)]
pub enum Viewtype {
Unknown = 0,
@@ -224,6 +202,23 @@ mod tests {
}
}
impl ToSql for Viewtype {
fn to_sql(&self) -> sql::Result<ToSqlOutput> {
let num: i64 = self
.to_i64()
.expect("impossible: Viewtype -> i64 conversion failed");
Ok(ToSqlOutput::Owned(Value::Integer(num)))
}
}
impl FromSql for Viewtype {
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
let inner = FromSql::column_result(col)?;
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
}
}
// These constants are used as events
// reported to the callback given to dc_context_new().
// If you do not want to handle an event, it is always safe to return 0,
@@ -282,7 +277,7 @@ pub enum Event {
/// 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())
/// However, for ongoing processes (eg. dc_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
@@ -400,7 +395,7 @@ pub enum Event {
/// @return 0
LOCATION_CHANGED = 2035,
/// Inform about the configuration progress started by configure().
/// Inform about the configuration progress started by dc_configure().
///
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
@@ -465,65 +460,65 @@ pub enum Event {
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;
const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
pub const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
pub const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
pub const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
pub const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
pub const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
/// Values for dc_get|set_config("show_emails")
const DC_SHOW_EMAILS_OFF: usize = 0;
const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
const DC_SHOW_EMAILS_ALL: usize = 2;
pub const DC_SHOW_EMAILS_OFF: usize = 0;
pub const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
pub const DC_SHOW_EMAILS_ALL: usize = 2;
// TODO: Strings need some doumentation about used placeholders.
// These constants are used to request strings using #DC_EVENT_GET_STRING.
const DC_STR_NOMESSAGES: usize = 1;
const DC_STR_SELF: usize = 2;
const DC_STR_DRAFT: usize = 3;
const DC_STR_MEMBER: usize = 4;
const DC_STR_CONTACT: usize = 6;
const DC_STR_VOICEMESSAGE: usize = 7;
const DC_STR_DEADDROP: usize = 8;
const DC_STR_IMAGE: usize = 9;
const DC_STR_VIDEO: usize = 10;
const DC_STR_AUDIO: usize = 11;
const DC_STR_FILE: usize = 12;
const DC_STR_STATUSLINE: usize = 13;
const DC_STR_NEWGROUPDRAFT: usize = 14;
const DC_STR_MSGGRPNAME: usize = 15;
const DC_STR_MSGGRPIMGCHANGED: usize = 16;
const DC_STR_MSGADDMEMBER: usize = 17;
const DC_STR_MSGDELMEMBER: usize = 18;
const DC_STR_MSGGROUPLEFT: usize = 19;
const DC_STR_GIF: usize = 23;
const DC_STR_ENCRYPTEDMSG: usize = 24;
const DC_STR_E2E_AVAILABLE: usize = 25;
const DC_STR_ENCR_TRANSP: usize = 27;
const DC_STR_ENCR_NONE: usize = 28;
const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
const DC_STR_FINGERPRINTS: usize = 30;
const DC_STR_READRCPT: usize = 31;
const DC_STR_READRCPT_MAILBODY: usize = 32;
const DC_STR_MSGGRPIMGDELETED: usize = 33;
const DC_STR_E2E_PREFERRED: usize = 34;
const DC_STR_CONTACT_VERIFIED: usize = 35;
const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
const DC_STR_ARCHIVEDCHATS: usize = 40;
const DC_STR_STARREDMSGS: usize = 41;
const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
const DC_STR_SELFTALK_SUBTITLE: usize = 50;
const DC_STR_CANNOT_LOGIN: usize = 60;
const DC_STR_SERVER_RESPONSE: usize = 61;
const DC_STR_MSGACTIONBYUSER: usize = 62;
const DC_STR_MSGACTIONBYME: usize = 63;
const DC_STR_MSGLOCATIONENABLED: usize = 64;
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
const DC_STR_LOCATION: usize = 66;
const DC_STR_COUNT: usize = 66;
pub const DC_STR_NOMESSAGES: usize = 1;
pub const DC_STR_SELF: usize = 2;
pub const DC_STR_DRAFT: usize = 3;
pub const DC_STR_MEMBER: usize = 4;
pub const DC_STR_CONTACT: usize = 6;
pub const DC_STR_VOICEMESSAGE: usize = 7;
pub const DC_STR_DEADDROP: usize = 8;
pub const DC_STR_IMAGE: usize = 9;
pub const DC_STR_VIDEO: usize = 10;
pub const DC_STR_AUDIO: usize = 11;
pub const DC_STR_FILE: usize = 12;
pub const DC_STR_STATUSLINE: usize = 13;
pub const DC_STR_NEWGROUPDRAFT: usize = 14;
pub const DC_STR_MSGGRPNAME: usize = 15;
pub const DC_STR_MSGGRPIMGCHANGED: usize = 16;
pub const DC_STR_MSGADDMEMBER: usize = 17;
pub const DC_STR_MSGDELMEMBER: usize = 18;
pub const DC_STR_MSGGROUPLEFT: usize = 19;
pub const DC_STR_GIF: usize = 23;
pub const DC_STR_ENCRYPTEDMSG: usize = 24;
pub const DC_STR_E2E_AVAILABLE: usize = 25;
pub const DC_STR_ENCR_TRANSP: usize = 27;
pub const DC_STR_ENCR_NONE: usize = 28;
pub const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
pub const DC_STR_FINGERPRINTS: usize = 30;
pub const DC_STR_READRCPT: usize = 31;
pub const DC_STR_READRCPT_MAILBODY: usize = 32;
pub const DC_STR_MSGGRPIMGDELETED: usize = 33;
pub const DC_STR_E2E_PREFERRED: usize = 34;
pub const DC_STR_CONTACT_VERIFIED: usize = 35;
pub const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
pub const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
pub const DC_STR_ARCHIVEDCHATS: usize = 40;
pub const DC_STR_STARREDMSGS: usize = 41;
pub const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
pub const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
pub const DC_STR_SELFTALK_SUBTITLE: usize = 50;
pub const DC_STR_CANNOT_LOGIN: usize = 60;
pub const DC_STR_SERVER_RESPONSE: usize = 61;
pub const DC_STR_MSGACTIONBYUSER: usize = 62;
pub const DC_STR_MSGACTIONBYME: usize = 63;
pub const DC_STR_MSGLOCATIONENABLED: usize = 64;
pub const DC_STR_MSGLOCATIONDISABLED: usize = 65;
pub const DC_STR_LOCATION: usize = 66;
pub const DC_STR_COUNT: usize = 66;
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
@@ -539,6 +534,6 @@ 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_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;
pub const DC_CMD_LOCATION_ONLY: libc::c_int = 9;

View File

@@ -7,12 +7,12 @@ use crate::aheader::EncryptPreference;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_array::*;
use crate::dc_e2ee::*;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::error::Result;
use crate::key::*;
use crate::message::MessageState;
use crate::peerstate::*;
use crate::sql;
use crate::stock::StockMessage;
@@ -139,7 +139,7 @@ pub enum Modifier {
Created,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub enum VerifiedStatus {
/// Contact is not verified.
@@ -152,7 +152,7 @@ pub enum VerifiedStatus {
impl<'a> Contact<'a> {
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
if contact_id == DC_CONTACT_ID_SELF as u32 {
let contact = Contact {
context,
id: contact_id,
@@ -251,7 +251,7 @@ impl<'a> Contact<'a> {
context,
&context.sql,
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
params![MessageState::InNoticed, id as i32, MessageState::InFresh],
params![DC_STATE_IN_NOTICED, id as i32, DC_STATE_IN_FRESH],
)
.is_ok()
{
@@ -328,7 +328,7 @@ impl<'a> Contact<'a> {
"<unset>"
},
);
bail!("Bad address supplied: {:?}", addr);
bail!("Bad address supplied");
}
let mut update_addr = false;
@@ -472,13 +472,13 @@ impl<'a> Contact<'a> {
context: &Context,
listflags: u32,
query: Option<impl AsRef<str>>,
) -> Result<Vec<u32>> {
) -> Result<*mut dc_array_t> {
let self_addr = context
.get_config(Config::ConfiguredAddr)
.unwrap_or_default();
let mut add_self = false;
let mut ret = Vec::new();
let mut ret = dc_array_t::new(100);
if (listflags & DC_GCL_VERIFIED_ONLY) > 0 || query.is_some() {
let s3str_like_cmd = format!(
@@ -509,7 +509,7 @@ impl<'a> Contact<'a> {
|row| row.get::<_, i32>(0),
|ids| {
for id in ids {
ret.push(id? as u32);
ret.add_id(id? as u32);
}
Ok(())
},
@@ -537,7 +537,7 @@ impl<'a> Contact<'a> {
|row| row.get::<_, i32>(0),
|ids| {
for id in ids {
ret.push(id? as u32);
ret.add_id(id? as u32);
}
Ok(())
}
@@ -545,10 +545,10 @@ impl<'a> Contact<'a> {
}
if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self {
ret.push(DC_CONTACT_ID_SELF);
ret.add_id(DC_CONTACT_ID_SELF as u32);
}
Ok(ret)
Ok(ret.into_raw())
}
pub fn get_blocked_cnt(context: &Context) -> usize {
@@ -564,27 +564,27 @@ impl<'a> Contact<'a> {
}
/// Get blocked contacts.
pub fn get_all_blocked(context: &Context) -> Vec<u32> {
pub fn get_all_blocked(context: &Context) -> *mut dc_array_t {
context
.sql
.query_map(
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|row| row.get::<_, u32>(0),
|row| row.get::<_, i32>(0),
|ids| {
ids.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
let mut ret = dc_array_t::new(100);
for id in ids {
ret.add_id(id? as u32);
}
Ok(ret.into_raw())
},
)
.unwrap_or_default()
.unwrap_or_else(|_| std::ptr::null_mut())
}
/// Returns a textual summary of the encryption state for the contact.
///
/// This function returns a string explaining the encryption state
/// of the contact and if the connection is encrypted the
/// fingerprints of the keys involved.
pub fn get_encrinfo(context: &Context, contact_id: u32) -> Result<String> {
pub fn get_encrinfo(context: &Context, contact_id: u32) -> String {
let mut ret = String::new();
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
@@ -603,7 +603,7 @@ impl<'a> Contact<'a> {
});
ret += &p;
if self_key.is_none() {
dc_ensure_secret_key_exists(context)?;
unsafe { dc_ensure_secret_key_exists(context) };
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
}
let p = context.stock_str(StockMessage::FingerPrints);
@@ -646,7 +646,7 @@ impl<'a> Contact<'a> {
}
}
Ok(ret)
ret
}
/// Delete a contact. The contact is deleted from the local device. It may happen that this is not
@@ -655,7 +655,7 @@ impl<'a> Contact<'a> {
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
pub fn delete(context: &Context, contact_id: u32) -> Result<()> {
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
contact_id > DC_CONTACT_ID_LAST_SPECIAL as u32,
"Can not delete special contact"
);
@@ -780,7 +780,7 @@ impl<'a> Contact<'a> {
/// 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) -> Option<String> {
if self.id == DC_CONTACT_ID_SELF {
if self.id == DC_CONTACT_ID_SELF as u32 {
return self.context.get_config(Config::Selfavatar);
}
// TODO: else get image_abs from contact param
@@ -792,7 +792,7 @@ impl<'a> Contact<'a> {
/// and can be used for an fallback avatar with white initials
/// as well as for headlines in bubbles of group chats.
pub fn get_color(&self) -> u32 {
dc_str_to_color(&self.addr)
dc_str_to_color_safe(&self.addr)
}
/// Check if a contact was verified. E.g. by a secure-join QR code scan
@@ -810,7 +810,7 @@ impl<'a> Contact<'a> {
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 {
if self.id == DC_CONTACT_ID_SELF as u32 {
return VerifiedStatus::BidirectVerified;
}
@@ -904,14 +904,26 @@ impl<'a> Contact<'a> {
}
}
fn get_first_name<'a>(full_name: &'a str) -> &'a str {
pub fn get_first_name<'a>(full_name: &'a str) -> &'a str {
full_name.splitn(2, ' ').next().unwrap_or_default()
}
/// Returns false if addr is an invalid address, otherwise true.
pub fn may_be_valid_addr(addr: &str) -> bool {
let res = addr.parse::<EmailAddress>();
res.is_ok()
if addr.is_empty() {
return false;
}
let at = addr.find('@').unwrap_or_default();
if at < 1 {
return false;
}
let dot = addr.find('.').unwrap_or_default();
if dot < 1 || dot > addr.len() - 3 || dot < at + 2 {
return false;
}
true
}
pub fn addr_normalize(addr: &str) -> &str {

View File

@@ -1,37 +1,36 @@
use std::sync::{Arc, Condvar, Mutex, RwLock};
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_job::*;
use crate::dc_jobthread::*;
use crate::dc_loginparam::*;
use crate::dc_lot::dc_lot_t;
use crate::dc_move::*;
use crate::dc_msg::*;
use crate::dc_receive_imf::*;
use crate::dc_tools::*;
use crate::imap::*;
use crate::job::*;
use crate::job_thread::JobThread;
use crate::key::*;
use crate::lot::Lot;
use crate::message::*;
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use crate::types::*;
use crate::x::*;
use std::ptr;
use std::path::PathBuf;
#[repr(C)]
pub struct Context {
pub userdata: *mut libc::c_void,
pub dbfile: Arc<RwLock<Option<PathBuf>>>,
pub dbfile: Arc<RwLock<*mut libc::c_char>>,
pub blobdir: Arc<RwLock<*mut libc::c_char>>,
pub sql: Sql,
pub inbox: Arc<RwLock<Imap>>,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
pub probe_imap_network: Arc<RwLock<bool>>,
pub sentbox_thread: Arc<RwLock<JobThread>>,
pub mvbox_thread: Arc<RwLock<JobThread>>,
pub sentbox_thread: Arc<RwLock<dc_jobthread_t>>,
pub mvbox_thread: Arc<RwLock<dc_jobthread_t>>,
pub smtp: Arc<Mutex<Smtp>>,
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
pub oauth2_critical: Arc<Mutex<()>>,
@@ -41,8 +40,6 @@ pub struct Context {
pub bob: Arc<RwLock<BobStatus>>,
pub last_smeared_timestamp: Arc<RwLock<i64>>,
pub running_state: Arc<RwLock<RunningState>>,
/// Mutex to avoid generating the key for the user more than once.
pub generating_key_mutex: Mutex<()>,
}
unsafe impl std::marker::Send for Context {}
@@ -56,17 +53,15 @@ pub struct RunningState {
impl Context {
pub fn has_dbfile(&self) -> bool {
self.get_dbfile().is_some()
!self.get_dbfile().is_null()
}
pub fn has_blobdir(&self) -> bool {
!self.get_blobdir().is_null()
}
pub fn get_dbfile(&self) -> Option<PathBuf> {
(*self.dbfile.clone().read().unwrap())
.as_ref()
.map(|x| x.clone())
pub fn get_dbfile(&self) -> *const libc::c_char {
*self.dbfile.clone().read().unwrap()
}
pub fn get_blobdir(&self) -> *const libc::c_char {
@@ -82,14 +77,6 @@ impl Context {
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
dc_close(&self);
}
}
}
impl Default for RunningState {
fn default() -> Self {
RunningState {
@@ -99,11 +86,21 @@ impl Default for RunningState {
}
}
#[derive(Default)]
#[derive(Debug, PartialEq, Eq)]
pub struct BobStatus {
pub expects: i32,
pub status: i32,
pub qr_scan: Option<Lot>,
pub qr_scan: *mut dc_lot_t,
}
impl Default for BobStatus {
fn default() -> Self {
BobStatus {
expects: 0,
status: 0,
qr_scan: std::ptr::null_mut(),
}
}
}
#[derive(Default, Debug)]
@@ -123,7 +120,7 @@ pub fn dc_context_new(
) -> Context {
Context {
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
dbfile: Arc::new(RwLock::new(None)),
dbfile: Arc::new(RwLock::new(std::ptr::null_mut())),
inbox: Arc::new(RwLock::new({
Imap::new(
cb_get_config,
@@ -134,7 +131,7 @@ pub fn dc_context_new(
})),
userdata,
cb,
os_name,
os_name: os_name,
running_state: Arc::new(RwLock::new(Default::default())),
sql: Sql::new(),
smtp: Arc::new(Mutex::new(Smtp::new())),
@@ -143,7 +140,7 @@ pub fn dc_context_new(
bob: Arc::new(RwLock::new(Default::default())),
last_smeared_timestamp: Arc::new(RwLock::new(0)),
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
sentbox_thread: Arc::new(RwLock::new(dc_jobthread_init(
"SENTBOX",
"configured_sentbox_folder",
Imap::new(
@@ -153,7 +150,7 @@ pub fn dc_context_new(
cb_receive_imf,
),
))),
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
mvbox_thread: Arc::new(RwLock::new(dc_jobthread_init(
"MVBOX",
"configured_mvbox_folder",
Imap::new(
@@ -165,7 +162,16 @@ pub fn dc_context_new(
))),
probe_imap_network: Arc::new(RwLock::new(false)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
generating_key_mutex: Mutex::new(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_crashes_on_context_deref() {
let mut ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
unsafe { dc_context_unref(&mut ctx) };
}
}
@@ -195,7 +201,7 @@ unsafe fn cb_precheck_imf(
) -> libc::c_int {
let mut rfc724_mid_exists: libc::c_int = 0i32;
let msg_id: uint32_t;
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
let mut old_server_folder: *mut libc::c_char = 0 as *mut libc::c_char;
let mut old_server_uid: uint32_t = 0i32 as uint32_t;
let mut mark_seen: libc::c_int = 0i32;
msg_id = dc_rfc724_mid_exists(
@@ -223,24 +229,18 @@ unsafe fn cb_precheck_imf(
"[move] detected moved message {}",
as_str(rfc724_mid),
);
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
dc_update_msg_move_state(context, rfc724_mid, DC_MOVE_STATE_STAY);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
dc_do_heuristics_moves(context, server_folder, msg_id);
if 0 != mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
msg_id as libc::c_int,
Params::new(),
0,
);
dc_job_add(context, 130, msg_id as libc::c_int, Params::new(), 0);
}
}
free(old_server_folder as *mut libc::c_void);
rfc724_mid_exists
return rfc724_mid_exists;
}
fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
@@ -258,6 +258,12 @@ fn cb_get_config(context: &Context, key: &str) -> Option<String> {
context.sql.get_config(context, key)
}
pub unsafe fn dc_context_unref(context: &mut Context) {
if 0 != dc_is_open(context) {
dc_close(context);
}
}
pub unsafe fn dc_close(context: &Context) {
info!(context, 0, "disconnecting INBOX-watch",);
context.inbox.read().unwrap().disconnect(context);
@@ -281,14 +287,18 @@ pub unsafe fn dc_close(context: &Context) {
context.sql.close(context);
let mut dbfile = context.dbfile.write().unwrap();
*dbfile = None;
free(*dbfile as *mut libc::c_void);
*dbfile = 0 as *mut libc::c_char;
let mut blobdir = context.blobdir.write().unwrap();
free(*blobdir as *mut libc::c_void);
*blobdir = ptr::null_mut();
*blobdir = 0 as *mut libc::c_char;
}
pub unsafe fn dc_is_open(context: &Context) -> libc::c_int {
context.sql.is_open() as libc::c_int
match context.sql.is_open() {
true => 1,
false => 0,
}
}
pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void {
@@ -300,16 +310,14 @@ pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) ->
if 0 != dc_is_open(context) {
return false;
}
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
let blobdir = blobdir.unwrap_or_default();
if !blobdir.is_empty() {
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
*context.dbfile.write().unwrap() = dbfile.strdup();
if blobdir.is_some() && blobdir.unwrap().len() > 0 {
let dir = dc_ensure_no_slash_safe(blobdir.unwrap()).strdup();
*context.blobdir.write().unwrap() = dir;
} else {
let dir = dbfile.to_string() + "-blobs";
dc_create_folder(context, &dir);
*context.blobdir.write().unwrap() = dir.strdup();
let dir = (dbfile.to_string() + "-blobs").strdup();
dc_create_folder(context, dir);
*context.blobdir.write().unwrap() = dir;
}
// Create/open sqlite database, this may already use the blobdir
let dbfile_path = std::path::Path::new(dbfile);
@@ -335,7 +343,7 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
let l = dc_loginparam_read(context, &context.sql, "");
let l2 = dc_loginparam_read(context, &context.sql, "configured_");
let displayname = context.sql.get_config(context, "displayname");
let chats = get_chat_cnt(context) as usize;
let chats = dc_get_chat_cnt(context) as usize;
let real_msgs = dc_get_real_msg_cnt(context) as usize;
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(context) as usize;
let contacts = Contact::get_real_cnt(context) as usize;
@@ -437,7 +445,7 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
public_key_count={}\n\
fingerprint={}\n\
level=awesome\n",
&*DC_VERSION_STR,
as_str(DC_VERSION_STR as *const u8 as *const _),
rusqlite::version(),
sqlite3_threadsafe(),
// arch
@@ -446,10 +454,11 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
real_msgs,
deaddrop_msgs,
contacts,
context
.get_dbfile()
.as_ref()
.map_or(unset, |p| p.to_str().unwrap()),
if context.has_dbfile() {
as_str(context.get_dbfile())
} else {
unset
},
dbversion,
if context.has_blobdir() {
as_str(context.get_blobdir())
@@ -478,10 +487,10 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
}
pub unsafe fn dc_get_version_str() -> *mut libc::c_char {
(&*DC_VERSION_STR).strdup()
dc_strdup(DC_VERSION_STR as *const u8 as *const libc::c_char)
}
pub fn dc_get_fresh_msgs(context: &Context) -> Vec<u32> {
pub fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
let show_deaddrop = 0;
context
@@ -496,12 +505,13 @@ pub fn dc_get_fresh_msgs(context: &Context) -> Vec<u32> {
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get(0),
|rows| {
let mut ret = Vec::new();
let mut ret = dc_array_t::new(128);
for row in rows {
let id: u32 = row?;
ret.push(id);
let id = row?;
ret.add_id(id);
}
Ok(ret)
Ok(ret.into_raw())
},
)
.unwrap()
@@ -512,14 +522,14 @@ pub fn dc_search_msgs(
context: &Context,
chat_id: uint32_t,
query: *const libc::c_char,
) -> Vec<u32> {
) -> *mut dc_array_t {
if query.is_null() {
return Vec::new();
return std::ptr::null_mut();
}
let real_query = to_string(query).trim().to_string();
if real_query.is_empty() {
return Vec::new();
return std::ptr::null_mut();
}
let strLikeInText = format!("%{}%", &real_query);
let strLikeBeg = format!("{}%", &real_query);
@@ -535,21 +545,28 @@ pub fn dc_search_msgs(
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
};
context
let mut ret = dc_array_t::new(100);
let success = context
.sql
.query_map(
query,
params![chat_id as libc::c_int, &strLikeInText, &strLikeBeg],
|row| row.get::<_, i32>(0),
|rows| {
let mut ret = Vec::new();
for id in rows {
ret.push(id? as u32);
ret.add_id(id? as u32);
}
Ok(ret)
Ok(())
},
)
.unwrap_or_default()
.is_ok();
if success {
return ret.into_raw();
}
std::ptr::null_mut()
}
pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
@@ -574,24 +591,3 @@ pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_crashes_on_context_deref() {
let ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
std::mem::drop(ctx);
}
#[test]
fn test_context_double_close() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
unsafe {
dc_close(&ctx);
dc_close(&ctx);
}
std::mem::drop(ctx);
}
}

View File

@@ -1,12 +1,13 @@
use crate::location::Location;
use crate::dc_location::dc_location;
use crate::dc_tools::*;
use crate::types::*;
/* * the structure behind dc_array_t */
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub enum dc_array_t {
Locations(Vec<Location>),
Uint(Vec<u32>),
Locations(Vec<dc_location>),
Uint(Vec<uintptr_t>),
}
impl dc_array_t {
@@ -19,15 +20,23 @@ impl dc_array_t {
dc_array_t::Locations(Vec::with_capacity(capacity))
}
pub fn add_id(&mut self, item: uint32_t) {
pub fn into_raw(self) -> *mut Self {
Box::into_raw(Box::new(self))
}
pub fn add_uint(&mut self, item: uintptr_t) {
if let Self::Uint(array) = self {
array.push(item);
} else {
panic!("Attempt to add id to array of other type");
panic!("Attempt to add uint to array of other type");
}
}
pub fn add_location(&mut self, location: Location) {
pub fn add_id(&mut self, item: uint32_t) {
self.add_uint(item as uintptr_t);
}
pub fn add_location(&mut self, location: dc_location) {
if let Self::Locations(array) = self {
array.push(location)
} else {
@@ -35,6 +44,14 @@ impl dc_array_t {
}
}
pub fn get_uint(&self, index: usize) -> uintptr_t {
if let Self::Uint(array) = self {
array[index]
} else {
panic!("Attempt to get uint from array of other type");
}
}
pub fn get_id(&self, index: usize) -> uint32_t {
match self {
Self::Locations(array) => array[index].location_id,
@@ -42,7 +59,15 @@ impl dc_array_t {
}
}
pub fn get_location(&self, index: usize) -> &Location {
pub fn get_ptr(&self, index: size_t) -> *mut libc::c_void {
if let Self::Uint(array) = self {
array[index] as *mut libc::c_void
} else {
panic!("Not an array of pointers");
}
}
pub fn get_location(&self, index: usize) -> &dc_location {
if let Self::Locations(array) = self {
&array[index]
} else {
@@ -50,6 +75,34 @@ impl dc_array_t {
}
}
pub fn get_latitude(&self, index: usize) -> libc::c_double {
self.get_location(index).latitude
}
pub fn get_longitude(&self, index: size_t) -> libc::c_double {
self.get_location(index).longitude
}
pub fn get_accuracy(&self, index: size_t) -> libc::c_double {
self.get_location(index).accuracy
}
pub fn get_timestamp(&self, index: size_t) -> i64 {
self.get_location(index).timestamp
}
pub fn get_chat_id(&self, index: size_t) -> uint32_t {
self.get_location(index).chat_id
}
pub fn get_contact_id(&self, index: size_t) -> uint32_t {
self.get_location(index).contact_id
}
pub fn get_msg_id(&self, index: size_t) -> uint32_t {
self.get_location(index).msg_id
}
pub fn is_empty(&self) -> bool {
match self {
Self::Locations(array) => array.is_empty(),
@@ -72,7 +125,7 @@ impl dc_array_t {
}
}
pub fn search_id(&self, needle: u32) -> Option<usize> {
pub fn search_id(&self, needle: uintptr_t) -> Option<usize> {
if let Self::Uint(array) = self {
for (i, &u) in array.iter().enumerate() {
if u == needle {
@@ -92,72 +145,297 @@ impl dc_array_t {
panic!("Attempt to sort array of something other than uints");
}
}
pub fn as_ptr(&self) -> *const u32 {
if let dc_array_t::Uint(v) = self {
v.as_ptr()
} else {
panic!("Attempt to convert array of something other than uints to raw");
}
}
}
impl From<Vec<u32>> for dc_array_t {
fn from(array: Vec<u32>) -> Self {
dc_array_t::Uint(array)
}
}
impl From<Vec<Location>> for dc_array_t {
fn from(array: Vec<Location>) -> Self {
impl From<Vec<dc_location>> for dc_array_t {
fn from(array: Vec<dc_location>) -> Self {
dc_array_t::Locations(array)
}
}
pub unsafe fn dc_array_unref(array: *mut dc_array_t) {
if array.is_null() {
return;
}
Box::from_raw(array);
}
pub unsafe fn dc_array_add_uint(array: *mut dc_array_t, item: uintptr_t) {
if !array.is_null() {
(*array).add_uint(item);
}
}
pub unsafe fn dc_array_add_id(array: *mut dc_array_t, item: uint32_t) {
if !array.is_null() {
(*array).add_id(item);
}
}
pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void) {
dc_array_add_uint(array, item as uintptr_t);
}
pub unsafe fn dc_array_get_cnt(array: *const dc_array_t) -> size_t {
if array.is_null() {
0
} else {
(*array).len()
}
}
pub unsafe fn dc_array_get_uint(array: *const dc_array_t, index: size_t) -> uintptr_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_uint(index)
}
}
pub unsafe fn dc_array_get_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_id(index)
}
}
pub unsafe fn dc_array_get_ptr(array: *const dc_array_t, index: size_t) -> *mut libc::c_void {
if array.is_null() || index >= (*array).len() {
std::ptr::null_mut()
} else {
(*array).get_ptr(index)
}
}
pub unsafe fn dc_array_get_latitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null() || index >= (*array).len() {
0.0
} else {
(*array).get_latitude(index)
}
}
pub unsafe fn dc_array_get_longitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null() || index >= (*array).len() {
0.0
} else {
(*array).get_longitude(index)
}
}
pub unsafe fn dc_array_get_accuracy(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null() || index >= (*array).len() {
0.0
} else {
(*array).get_accuracy(index)
}
}
pub unsafe fn dc_array_get_timestamp(array: *const dc_array_t, index: size_t) -> i64 {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_timestamp(index)
}
}
pub unsafe fn dc_array_get_chat_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_chat_id(index)
}
}
pub unsafe fn dc_array_get_contact_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_contact_id(index)
}
}
pub unsafe fn dc_array_get_msg_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_msg_id(index)
}
}
pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *mut libc::c_char {
if array.is_null() || index >= (*array).len() {
return std::ptr::null_mut();
}
if let dc_array_t::Locations(v) = &*array {
if let Some(s) = &v[index].marker {
s.strdup()
} else {
std::ptr::null_mut()
}
} else {
std::ptr::null_mut()
}
}
/**
* Return the independent-state of the location at the given index.
* Independent locations do not belong to the track of the user.
*
* @memberof dc_array_t
* @param array The array object.
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
* @return 0=Location belongs to the track of the user,
* 1=Location was reported independently.
*/
pub unsafe fn dc_array_is_independent(array: *const dc_array_t, index: size_t) -> libc::c_int {
if array.is_null() || index >= (*array).len() {
return 0;
}
if let dc_array_t::Locations(v) = &*array {
v[index].independent as libc::c_int
} else {
panic!("Attempt to get location independent field from array of something other than locations");
}
}
pub unsafe fn dc_array_search_id(
array: *const dc_array_t,
needle: uint32_t,
ret_index: *mut size_t,
) -> bool {
if array.is_null() {
return false;
}
if let Some(i) = (*array).search_id(needle as uintptr_t) {
if !ret_index.is_null() {
*ret_index = i
}
true
} else {
false
}
}
pub unsafe fn dc_array_get_raw(array: *const dc_array_t) -> *const uintptr_t {
if array.is_null() {
return 0 as *const uintptr_t;
}
if let dc_array_t::Uint(v) = &*array {
v.as_ptr()
} else {
panic!("Attempt to convert array of something other than uints to raw");
}
}
pub fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
dc_array_t::new(initsize).into_raw()
}
pub fn dc_array_new_locations(initsize: size_t) -> *mut dc_array_t {
dc_array_t::new_locations(initsize).into_raw()
}
pub unsafe fn dc_array_empty(array: *mut dc_array_t) {
if array.is_null() {
return;
}
(*array).clear()
}
pub unsafe fn dc_array_duplicate(array: *const dc_array_t) -> *mut dc_array_t {
if array.is_null() {
std::ptr::null_mut()
} else {
(*array).clone().into_raw()
}
}
pub unsafe fn dc_array_get_string(
array: *const dc_array_t,
sep: *const libc::c_char,
) -> *mut libc::c_char {
if array.is_null() || sep.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if let dc_array_t::Uint(v) = &*array {
let cnt = v.len();
let sep = as_str(sep);
let res = v
.iter()
.enumerate()
.fold(String::with_capacity(2 * cnt), |res, (i, n)| {
if i == 0 {
res + &n.to_string()
} else {
res + sep + &n.to_string()
}
});
res.strdup()
} else {
panic!("Attempt to get string from array of other type");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::x::*;
use std::ffi::CStr;
#[test]
fn test_dc_array() {
let mut arr = dc_array_t::new(7);
assert!(arr.is_empty());
unsafe {
let arr = dc_array_new(7 as size_t);
assert_eq!(dc_array_get_cnt(arr), 0);
for i in 0..1000 {
arr.add_id(i + 2);
for i in 0..1000 {
dc_array_add_id(arr, (i + 2) as uint32_t);
}
assert_eq!(dc_array_get_cnt(arr), 1000);
for i in 0..1000 {
assert_eq!(
dc_array_get_id(arr, i as size_t),
(i + 1i32 * 2i32) as libc::c_uint
);
}
assert_eq!(dc_array_get_id(arr, -1i32 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1000 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1001 as size_t), 0);
dc_array_empty(arr);
assert_eq!(dc_array_get_cnt(arr), 0);
dc_array_add_id(arr, 13 as uint32_t);
dc_array_add_id(arr, 7 as uint32_t);
dc_array_add_id(arr, 666 as uint32_t);
dc_array_add_id(arr, 0 as uint32_t);
dc_array_add_id(arr, 5000 as uint32_t);
(*arr).sort_ids();
assert_eq!(dc_array_get_id(arr, 0 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1 as size_t), 7);
assert_eq!(dc_array_get_id(arr, 2 as size_t), 13);
assert_eq!(dc_array_get_id(arr, 3 as size_t), 666);
let str = dc_array_get_string(arr, b"-\x00" as *const u8 as *const libc::c_char);
assert_eq!(
CStr::from_ptr(str as *const libc::c_char).to_str().unwrap(),
"0-7-13-666-5000"
);
free(str as *mut libc::c_void);
dc_array_unref(arr);
}
assert_eq!(arr.len(), 1000);
for i in 0..1000 {
assert_eq!(arr.get_id(i), (i + 2) as u32);
}
arr.clear();
assert!(arr.is_empty());
arr.add_id(13);
arr.add_id(7);
arr.add_id(666);
arr.add_id(0);
arr.add_id(5000);
arr.sort_ids();
assert_eq!(arr.get_id(0), 0);
assert_eq!(arr.get_id(1), 7);
assert_eq!(arr.get_id(2), 13);
assert_eq!(arr.get_id(3), 666);
}
#[test]
#[should_panic]
fn test_dc_array_out_of_bounds() {
let mut arr = dc_array_t::new(7);
for i in 0..1000 {
arr.add_id(i + 2);
}
arr.get_id(1000);
}
}

2275
src/dc_chat.rs Normal file

File diff suppressed because it is too large Load Diff

1535
src/dc_configure.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
use lazy_static::lazy_static;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::dc_saxparser::*;
use crate::dc_tools::*;
use crate::x::*;
lazy_static! {
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
@@ -21,79 +23,67 @@ enum AddText {
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
// the lineends are typically remove in further processing by the caller
pub fn dc_dehtml(buf_terminated: &str) -> String {
let buf_terminated = buf_terminated.trim();
if buf_terminated.is_empty() {
return "".into();
pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
dc_trim(buf_terminated);
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(buf_terminated.len()),
strbuilder: String::with_capacity(strlen(buf_terminated)),
add_text: AddText::YesRemoveLineEnds,
last_href: None,
};
let mut saxparser = dc_saxparser_t {
starttag_cb: None,
endtag_cb: None,
text_cb: None,
userdata: 0 as *mut libc::c_void,
};
dc_saxparser_init(
&mut saxparser,
&mut dehtml as *mut Dehtml as *mut libc::c_void,
);
dc_saxparser_set_tag_handler(
&mut saxparser,
Some(dehtml_starttag_cb),
Some(dehtml_endtag_cb),
);
dc_saxparser_set_text_handler(&mut saxparser, Some(dehtml_text_cb));
dc_saxparser_parse(&mut saxparser, buf_terminated);
let mut reader = quick_xml::Reader::from_str(buf_terminated);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
dehtml_starttag_cb(e, &mut dehtml, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => dehtml_endtag_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::Text(ref e)) => dehtml_text_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::CData(ref e)) => dehtml_cdata_cb(e, &mut dehtml),
Err(e) => {
eprintln!(
"Parse html error: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
dehtml.strbuilder
dehtml.strbuilder.strdup()
}
fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
unsafe fn dehtml_text_cb(
userdata: *mut libc::c_void,
text: *const libc::c_char,
_len: libc::c_int,
) {
let dehtml = &mut *(userdata as *mut Dehtml);
if dehtml.add_text == AddText::YesPreserveLineEnds
|| dehtml.add_text == AddText::YesRemoveLineEnds
{
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
let last_added = std::ffi::CStr::from_ptr(text)
.to_str()
.expect("invalid utf8");
// TODO: why does len does not match?
// assert_eq!(last_added.len(), len as usize);
if dehtml.add_text == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
dehtml.strbuilder += LINE_RE.replace_all(last_added.as_ref(), "\r").as_ref();
} else {
dehtml.strbuilder += &last_added;
dehtml.strbuilder += last_added.as_ref();
}
}
}
fn dehtml_cdata_cb(event: &BytesText, dehtml: &mut Dehtml) {
if dehtml.add_text == AddText::YesPreserveLineEnds
|| dehtml.add_text == AddText::YesRemoveLineEnds
{
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
let mut dehtml = &mut *(userdata as *mut Dehtml);
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
if dehtml.add_text == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
} else {
dehtml.strbuilder += &last_added;
}
}
}
fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
match tag.as_str() {
match tag.as_ref() {
"p" | "div" | "table" | "td" | "style" | "script" | "title" | "pre" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
@@ -115,14 +105,15 @@ fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
}
}
fn dehtml_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
dehtml: &mut Dehtml,
reader: &quick_xml::Reader<B>,
unsafe fn dehtml_starttag_cb(
userdata: *mut libc::c_void,
tag: *const libc::c_char,
attr: *mut *mut libc::c_char,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let mut dehtml = &mut *(userdata as *mut Dehtml);
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
match tag.as_str() {
match tag.as_ref() {
"p" | "div" | "table" | "td" => {
dehtml.strbuilder += "\n\n";
dehtml.add_text = AddText::YesRemoveLineEnds;
@@ -139,21 +130,14 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
dehtml.add_text = AddText::YesPreserveLineEnds;
}
"a" => {
if let Some(href) = event.html_attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "href")
.unwrap_or_default()
}) {
let href = href
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.to_lowercase();
if !href.is_empty() {
dehtml.last_href = Some(href);
dehtml.strbuilder += "[";
}
let text_c = std::ffi::CStr::from_ptr(dc_attr_find(
attr,
b"href\x00" as *const u8 as *const libc::c_char,
));
let text_r = text_c.to_str().expect("invalid utf8");
if !text_r.is_empty() {
dehtml.last_href = Some(text_r.to_string());
dehtml.strbuilder += "[";
}
}
"b" | "strong" => {

View File

@@ -1,8 +1,5 @@
//! End-to-end encryption support.
use std::collections::HashSet;
use std::ffi::CStr;
use std::ptr;
use std::str::FromStr;
use mmime::clist::*;
@@ -19,12 +16,10 @@ use mmime::mmapstring::*;
use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
use crate::aheader::*;
use crate::config::Config;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_securejoin::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::key::*;
use crate::keyring::*;
use crate::peerstate::*;
@@ -101,11 +96,9 @@ pub unsafe fn dc_e2ee_encrypt(
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, 0, "Failed to load public key: {}", err);
err
});
if let Ok(public_key) = pubkey_ret {
if let Some(public_key) =
load_or_generate_self_public_key(context, &addr, in_out_message)
{
/*only for random-seed*/
if prefer_encrypt == EncryptPreference::Mutual || 0 != e2ee_guaranteed {
do_encrypt = 1i32;
@@ -122,22 +115,11 @@ pub unsafe fn dc_e2ee_encrypt(
|| 0 != e2ee_guaranteed)
{
let peerstate = peerstate.unwrap();
info!(
context,
0, "dc_e2ee_encrypt {} has peerstate", recipient_addr
);
if let Some(key) = peerstate.peek_key(min_verified as usize) {
keyring.add_owned(key.clone());
peerstates.push(peerstate);
}
} else {
info!(
context,
0,
"dc_e2ee_encrypt {} HAS NO peerstate {}",
recipient_addr,
peerstate.is_some()
);
do_encrypt = 0i32;
/* if we cannot encrypt to a single recipient, we cannot encrypt the message at all */
break;
@@ -146,7 +128,7 @@ pub unsafe fn dc_e2ee_encrypt(
iter1 = if !iter1.is_null() {
(*iter1).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}
@@ -171,19 +153,19 @@ pub unsafe fn dc_e2ee_encrypt(
mailprivacy_prepare_mime(in_out_message);
let mut part_to_encrypt: *mut mailmime =
(*in_out_message).mm_data.mm_message.mm_msg_mime;
(*part_to_encrypt).mm_parent = ptr::null_mut();
(*part_to_encrypt).mm_parent = 0 as *mut mailmime;
let imffields_encrypted: *mut mailimf_fields = mailimf_fields_new_empty();
/* mailmime_new_message_data() calls mailmime_fields_new_with_version() which would add the unwanted MIME-Version:-header */
let message_to_encrypt: *mut mailmime = mailmime_new(
MAILMIME_MESSAGE as libc::c_int,
ptr::null(),
0 as *const libc::c_char,
0i32 as size_t,
mailmime_fields_new_empty(),
mailmime_get_content_message(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut mailmime_data,
0 as *mut mailmime_data,
0 as *mut mailmime_data,
0 as *mut clist,
imffields_encrypted,
part_to_encrypt,
);
@@ -215,7 +197,7 @@ pub unsafe fn dc_e2ee_encrypt(
let field: *mut mailimf_field = (if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
})
as *mut mailimf_field;
if !field.is_null() {
@@ -255,7 +237,7 @@ pub unsafe fn dc_e2ee_encrypt(
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}
@@ -266,28 +248,28 @@ pub unsafe fn dc_e2ee_encrypt(
imffields_unprotected,
mailimf_field_new(
MAILIMF_FIELD_SUBJECT as libc::c_int,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut mailimf_return,
0 as *mut mailimf_orig_date,
0 as *mut mailimf_from,
0 as *mut mailimf_sender,
0 as *mut mailimf_to,
0 as *mut mailimf_cc,
0 as *mut mailimf_bcc,
0 as *mut mailimf_message_id,
0 as *mut mailimf_orig_date,
0 as *mut mailimf_from,
0 as *mut mailimf_sender,
0 as *mut mailimf_reply_to,
0 as *mut mailimf_to,
0 as *mut mailimf_cc,
0 as *mut mailimf_bcc,
0 as *mut mailimf_message_id,
0 as *mut mailimf_in_reply_to,
0 as *mut mailimf_references,
subject,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut mailimf_comments,
0 as *mut mailimf_keywords,
0 as *mut mailimf_optional_field,
),
);
clist_insert_after(
@@ -315,7 +297,7 @@ pub unsafe fn dc_e2ee_encrypt(
/* create MIME-structure that will contain the encrypted text */
let mut encrypted_part: *mut mailmime = new_data_part(
ptr::null_mut(),
0 as *mut libc::c_void,
0i32 as size_t,
b"multipart/encrypted\x00" as *const u8 as *const libc::c_char
as *mut libc::c_char,
@@ -401,7 +383,7 @@ unsafe fn new_data_part(
let encoding_type: libc::c_int;
let content_type_str: *mut libc::c_char;
let mut do_encoding: libc::c_int;
encoding = ptr::null_mut();
encoding = 0 as *mut mailmime_mechanism;
if default_content_type.is_null() {
content_type_str =
b"application/octet-stream\x00" as *const u8 as *const libc::c_char as *mut libc::c_char
@@ -436,7 +418,7 @@ unsafe fn new_data_part(
} else {
encoding_type = default_encoding
}
encoding = mailmime_mechanism_new(encoding_type, ptr::null_mut());
encoding = mailmime_mechanism_new(encoding_type, 0 as *mut libc::c_char);
if encoding.is_null() {
ok_to_continue = false;
}
@@ -444,10 +426,10 @@ unsafe fn new_data_part(
if ok_to_continue {
mime_fields = mailmime_fields_new_with_data(
encoding,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut libc::c_char,
0 as *mut libc::c_char,
0 as *mut mailmime_disposition,
0 as *mut mailmime_language,
);
if mime_fields.is_null() {
ok_to_continue = false;
@@ -477,55 +459,68 @@ unsafe fn new_data_part(
mailmime_content_free(content);
}
}
ptr::null_mut()
return 0 as *mut mailmime;
}
/// Load public key from database or generate a new one.
///
/// This will load a public key from the database, generating and
/// storing a new one when one doesn't exist yet. Care is taken to
/// only generate one key per context even when multiple threads call
/// this function concurrently.
fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str>) -> Result<Key> {
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) {
return Ok(key);
}
let _guard = context.generating_key_mutex.lock().unwrap();
/*******************************************************************************
* Generate Keypairs
******************************************************************************/
unsafe fn load_or_generate_self_public_key(
context: &Context,
self_addr: impl AsRef<str>,
_random_data_mime: *mut mailmime,
) -> Option<Key> {
/* avoid double creation (we unlock the database during creation) */
static mut S_IN_KEY_CREATION: libc::c_int = 0;
// Check again in case the key was generated while we were waiting for the lock.
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) {
return Ok(key);
let mut key = Key::from_self_public(context, &self_addr, &context.sql);
if key.is_some() {
return key;
}
let start = std::time::Instant::now();
/* create the keypair - this may take a moment, however, as this is in a thread, this is no big deal */
if 0 != S_IN_KEY_CREATION {
return None;
}
let key_creation_here = 1;
S_IN_KEY_CREATION = 1;
let start = clock();
info!(
context,
0, "Generating keypair with {} bits, e={} ...", 2048, 65537,
);
match dc_pgp_create_keypair(&self_addr) {
Some((public_key, private_key)) => {
match dc_key_save_self_keypair(
if let Some((public_key, private_key)) = dc_pgp_create_keypair(&self_addr) {
if !dc_key_save_self_keypair(
context,
&public_key,
&private_key,
&self_addr,
1i32,
&context.sql,
) {
/*set default*/
warn!(context, 0, "Cannot save keypair.",);
} else {
info!(
context,
&public_key,
&private_key,
&self_addr,
1,
&context.sql,
) {
true => {
info!(
context,
0,
"Keypair generated in {:.3}s.",
start.elapsed().as_secs()
);
Ok(public_key)
}
false => Err(format_err!("Failed to save keypair")),
}
0,
"Keypair generated in {:.3}s.",
clock().wrapping_sub(start) as libc::c_double / 1000000 as libc::c_double,
);
}
None => Err(format_err!("Failed to generate keypair")),
key = Some(public_key);
} else {
warn!(context, 0, "Cannot create keypair.");
}
if 0 != key_creation_here {
S_IN_KEY_CREATION = 0;
}
key
}
/* returns 1 if sth. was decrypted, 0 in other cases */
@@ -540,23 +535,25 @@ pub unsafe fn dc_e2ee_decrypt(
/*just a pointer into mailmime structure, must not be freed*/
let imffields: *mut mailimf_fields = mailmime_find_mailimf_fields(in_out_message);
let mut message_time = 0;
let mut from: *mut libc::c_char = ptr::null_mut();
let mut from: *mut libc::c_char = 0 as *mut libc::c_char;
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers: *mut mailimf_fields = ptr::null_mut();
let mut gossip_headers: *mut mailimf_fields = 0 as *mut mailimf_fields;
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field: *mut mailimf_field =
mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
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;
if !orig_date.is_null() {
message_time = dc_timestamp_from_date((*orig_date).dt_date_time);
if message_time != 0 && message_time > time() {
message_time = time()
if !imffields.is_null() {
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;
if !orig_date.is_null() {
message_time = dc_timestamp_from_date((*orig_date).dt_date_time);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
}
@@ -577,7 +574,7 @@ pub unsafe fn dc_e2ee_decrypt(
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
assert!(p.save_to_db(&context.sql, true));
p.save_to_db(&context.sql, true);
peerstate = Some(p);
}
}
@@ -647,7 +644,7 @@ unsafe fn update_gossip_peerstates(
let field: *mut mailimf_field = (if !cur1.is_null() {
(*cur1).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailimf_field;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *const mailimf_optional_field =
@@ -699,7 +696,7 @@ unsafe fn update_gossip_peerstates(
cur1 = if !cur1.is_null() {
(*cur1).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
@@ -732,13 +729,13 @@ unsafe fn decrypt_recursive(
{
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
while !cur.is_null() {
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
let mut decrypted_mime: *mut mailmime = 0 as *mut mailmime;
if 0 != decrypt_part(
context,
(if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailmime,
private_keyring,
public_keyring_for_validate,
@@ -747,7 +744,7 @@ unsafe fn decrypt_recursive(
) {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
let mut dummy: size_t = 0i32 as size_t;
let mut test: *mut mailimf_fields = ptr::null_mut();
let mut test: *mut mailimf_fields = 0 as *mut mailimf_fields;
if mailimf_envelope_and_optional_fields_parse(
(*decrypted_mime).mm_mime_start,
(*decrypted_mime).mm_length,
@@ -766,7 +763,7 @@ unsafe fn decrypt_recursive(
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
*ret_has_unencrypted_parts = 1i32
@@ -778,7 +775,7 @@ unsafe fn decrypt_recursive(
(if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailmime,
private_keyring,
public_keyring_for_validate,
@@ -791,7 +788,7 @@ unsafe fn decrypt_recursive(
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}
@@ -815,7 +812,7 @@ unsafe fn decrypt_recursive(
}
unsafe fn decrypt_part(
_context: &Context,
context: &Context,
mime: *mut mailmime,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
@@ -826,12 +823,12 @@ unsafe fn decrypt_part(
let mime_data: *mut mailmime_data;
let mut mime_transfer_encoding: libc::c_int = MAILMIME_MECHANISM_BINARY as libc::c_int;
/* mmap_string_unref()'d if set */
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
let mut transfer_decoding_buffer: *mut libc::c_char = 0 as *mut libc::c_char;
/* must not be free()'d */
let mut decoded_data: *const libc::c_char = ptr::null_mut();
let mut decoded_data: *const libc::c_char = 0 as *const libc::c_char;
let mut decoded_data_bytes: size_t = 0i32 as size_t;
let mut sth_decrypted: libc::c_int = 0i32;
*ret_decrypted_mime = ptr::null_mut();
*ret_decrypted_mime = 0 as *mut mailmime;
mime_data = (*mime).mm_data.mm_single;
/* MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing */
if !((*mime_data).dt_type != MAILMIME_DATA_TEXT as libc::c_int
@@ -845,7 +842,7 @@ unsafe fn decrypt_part(
let field: *mut mailmime_field = (if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailmime_field;
if !field.is_null() {
if (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
@@ -857,7 +854,7 @@ unsafe fn decrypt_part(
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}
@@ -913,7 +910,7 @@ unsafe fn decrypt_part(
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index: size_t = 0i32 as size_t;
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
let mut decrypted_mime: *mut mailmime = 0 as *mut mailmime;
if mailmime_parse(
plain_buf as *const _,
plain_bytes,
@@ -1011,7 +1008,7 @@ unsafe fn contains_report(mime: *mut mailmime) -> libc::c_int {
(if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailmime,
) {
return 1i32;
@@ -1019,7 +1016,7 @@ unsafe fn contains_report(mime: *mut mailmime) -> libc::c_int {
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
} else if (*mime).mm_type == MAILMIME_MESSAGE as libc::c_int {
@@ -1034,52 +1031,36 @@ unsafe fn contains_report(mime: *mut mailmime) -> libc::c_int {
/* frees data referenced by "mailmime" but not freed by mailmime_free(). After calling this function, in_out_message cannot be used any longer! */
pub unsafe fn dc_e2ee_thanks(helper: &mut dc_e2ee_helper_t) {
free(helper.cdata_to_free);
helper.cdata_to_free = ptr::null_mut();
helper.cdata_to_free = 0 as *mut libc::c_void;
}
/// Ensures a private key exists for the configured user.
///
/// Normally the private key is generated when the first message is
/// sent but in a few locations there are no such guarantees,
/// e.g. when exporting keys, and calling this function ensures a
/// private key will be present.
///
/// If this succeeds you are also guaranteed that the
/// [Config::ConfiguredAddr] is configured, this address is returned.
pub fn dc_ensure_secret_key_exists(context: &Context) -> Result<String> {
let self_addr = context
.get_config(Config::ConfiguredAddr)
.ok_or(format_err!(concat!(
"Failed to get self address, ",
"cannot ensure secret key if not configured."
)))?;
load_or_generate_self_public_key(context, &self_addr)?;
Ok(self_addr)
/* makes sure, the private key exists, needed only for exporting keys and the case no message was sent before */
// TODO should return bool /rtn
pub unsafe fn dc_ensure_secret_key_exists(context: &Context) -> libc::c_int {
/* normally, the key is generated as soon as the first mail is send
(this is to gain some extra-random-seed by the message content and the timespan between program start and message sending) */
let mut success: libc::c_int = 0i32;
let self_addr = context.sql.get_config(context, "configured_addr");
if self_addr.is_none() {
warn!(
context,
0, "Cannot ensure secret key if context is not configured.",
);
} else if load_or_generate_self_public_key(context, self_addr.unwrap(), 0 as *mut mailmime)
.is_some()
{
/*no random text data for seeding available*/
success = 1;
}
success
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
mod ensure_secret_key_exists {
use super::*;
#[test]
fn test_prexisting() {
let t = dummy_context();
let test_addr = configure_alice_keypair(&t.ctx);
assert_eq!(dc_ensure_secret_key_exists(&t.ctx).unwrap(), test_addr);
}
#[test]
fn test_not_configured() {
let t = dummy_context();
assert!(dc_ensure_secret_key_exists(&t.ctx).is_err());
}
}
#[test]
fn test_mailmime_parse() {
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
@@ -1111,9 +1092,9 @@ Sent with my Delta Chat Messenger: https://delta.chat";
};
unsafe {
let msg1 = (*decrypted_mime).mm_data.mm_message.mm_msg_mime;
let mut decoded_data = ptr::null();
let mut decoded_data = 0 as *const libc::c_char;
let mut decoded_data_bytes = 0;
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
let mut transfer_decoding_buffer: *mut libc::c_char = 0 as *mut libc::c_char;
assert_eq!(
mailmime_transfer_decode(
@@ -1140,47 +1121,4 @@ Sent with my Delta Chat Messenger: https://delta.chat";
unsafe { free(decrypted_mime as *mut _) };
}
mod load_or_generate_self_public_key {
use super::*;
#[test]
fn test_existing() {
let t = dummy_context();
let addr = configure_alice_keypair(&t.ctx);
let key = load_or_generate_self_public_key(&t.ctx, addr);
assert!(key.is_ok());
}
#[test]
#[ignore] // generating keys is expensive
fn test_generate() {
let t = dummy_context();
let addr = "alice@example.org";
let key0 = load_or_generate_self_public_key(&t.ctx, addr);
assert!(key0.is_ok());
let key1 = load_or_generate_self_public_key(&t.ctx, addr);
assert!(key1.is_ok());
assert_eq!(key0.unwrap(), key1.unwrap());
}
#[test]
#[ignore]
fn test_generate_concurrent() {
use std::sync::Arc;
use std::thread;
let t = dummy_context();
let ctx = Arc::new(t.ctx);
let ctx0 = Arc::clone(&ctx);
let thr0 =
thread::spawn(move || load_or_generate_self_public_key(&ctx0, "alice@example.org"));
let ctx1 = Arc::clone(&ctx);
let thr1 =
thread::spawn(move || load_or_generate_self_public_key(&ctx1, "alice@example.org"));
let res0 = thr0.join().unwrap();
let res1 = thr1.join().unwrap();
assert_eq!(res0.unwrap(), res1.unwrap());
}
}
}

View File

@@ -1,22 +1,21 @@
use std::ffi::CString;
use std::ptr;
use mmime::mailmime_content::*;
use mmime::mmapstring::*;
use mmime::other::*;
use rand::{thread_rng, Rng};
use crate::chat;
use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_configure::*;
use crate::dc_e2ee::*;
use crate::dc_job::*;
use crate::dc_msg::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::job::*;
use crate::key::*;
use crate::message::*;
use crate::param::*;
use crate::pgp::*;
use crate::sql::{self, Sql};
@@ -44,8 +43,8 @@ pub unsafe fn dc_imex(
param.set(Param::Arg2, as_str(param2));
}
job_kill_action(context, Action::ImexImap);
job_add(context, Action::ImexImap, 0, param, 0);
dc_job_kill_action(context, 910);
dc_job_add(context, 910, 0, param, 0);
}
/// Returns the filename of the backup if found, nullptr otherwise.
@@ -62,7 +61,7 @@ pub unsafe fn dc_imex_has_backup(
"Backup check: Cannot open directory \"{}\".\x00",
dir_name.display(),
);
return ptr::null_mut();
return 0 as *mut libc::c_char;
}
let mut newest_backup_time = 0;
let mut newest_backup_path: Option<std::path::PathBuf> = None;
@@ -101,8 +100,8 @@ 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;
let mut setup_file_name: *mut libc::c_char = 0 as *mut libc::c_char;
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
if dc_alloc_ongoing(context) == 0 {
return std::ptr::null_mut();
}
@@ -138,15 +137,17 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
setup_file_content_c.as_bytes().len(),
))
{
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
let chat_id = dc_create_chat_by_contact_id(context, 1i32 as uint32_t);
if !(chat_id == 0i32 as libc::c_uint) {
msg = dc_msg_new_untyped(context);
msg.type_0 = Viewtype::File;
msg.param.set(Param::File, as_str(setup_file_name));
(*msg).type_0 = Viewtype::File;
(*msg).param.set(Param::File, as_str(setup_file_name));
msg.param
(*msg)
.param
.set(Param::MimeType, "application/autocrypt-setup");
msg.param.set_int(Param::Cmd, 6);
msg.param.set_int(Param::ForcePlaintext, 2);
(*msg).param.set_int(Param::Cmd, 6);
(*msg).param.set_int(Param::ForcePlaintext, 2);
if !context
.running_state
@@ -155,7 +156,10 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
if let Ok(msg_id) = chat::send_msg(context, chat_id, &mut msg) {
let msg_id = dc_send_msg(context, chat_id, msg);
if msg_id != 0 {
dc_msg_unref(msg);
msg = 0 as *mut dc_msg_t;
info!(context, 0, "Wait for setup message being sent ...",);
loop {
if context
@@ -168,12 +172,13 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
break;
}
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(msg) = dc_get_msg(context, msg_id) {
if 0 != dc_msg_is_sent(&msg) {
info!(context, 0, "... setup message sent.",);
break;
}
msg = dc_get_msg(context, msg_id);
if 0 != dc_msg_is_sent(msg) {
info!(context, 0, "... setup message sent.",);
break;
}
dc_msg_unref(msg);
msg = 0 as *mut dc_msg_t
}
}
}
@@ -183,20 +188,26 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
}
free(setup_file_name as *mut libc::c_void);
dc_msg_unref(msg);
dc_free_ongoing(context);
setup_code.strdup()
}
/// Renders HTML body of a setup file message.
///
/// The `passphrase` must be at least 2 characters long.
pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
ensure!(
passphrase.len() >= 2,
"Passphrase must be at least 2 chars long."
);
let self_addr = dc_ensure_secret_key_exists(context)?;
unsafe {
ensure!(
!(dc_ensure_secret_key_exists(context) == 0),
"No secret key available."
);
}
let self_addr = context
.get_config(Config::ConfiguredAddr)
.ok_or(format_err!("Failed to get self address."))?;
let private_key = Key::from_self_private(context, self_addr, &context.sql)
.ok_or(format_err!("Failed to get private key."))?;
let ac_headers = match context
@@ -279,17 +290,18 @@ pub unsafe fn dc_continue_key_transfer(
setup_code: *const libc::c_char,
) -> 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 msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
let mut filecontent: *mut libc::c_char = 0 as *mut libc::c_char;
let mut filebytes: size_t = 0i32 as size_t;
let mut armored_key: *mut libc::c_char = ptr::null_mut();
let mut norm_sc: *mut libc::c_char = ptr::null_mut();
let mut armored_key: *mut libc::c_char = 0 as *mut libc::c_char;
let mut norm_sc: *mut libc::c_char = 0 as *mut libc::c_char;
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())
msg = dc_get_msg(context, msg_id);
if msg.is_null()
|| !dc_msg_is_setupmessage(msg)
|| {
filename = dc_msg_get_file(msg.as_ref().unwrap());
filename = dc_msg_get_file(msg);
filename.is_null()
}
|| *filename.offset(0isize) as libc::c_int == 0i32
@@ -325,6 +337,7 @@ pub unsafe fn dc_continue_key_transfer(
free(armored_key as *mut libc::c_void);
free(filecontent as *mut libc::c_void);
free(filename as *mut libc::c_void);
dc_msg_unref(msg);
free(norm_sc as *mut libc::c_void);
success
@@ -415,19 +428,19 @@ pub unsafe fn dc_decrypt_setup_file(
filecontent: *const libc::c_char,
) -> *mut libc::c_char {
let fc_buf: *mut libc::c_char;
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 fc_headerline: *const libc::c_char = 0 as *const libc::c_char;
let mut fc_base64: *const libc::c_char = 0 as *const libc::c_char;
let mut binary: *mut libc::c_char = 0 as *mut libc::c_char;
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();
let mut payload: *mut libc::c_char = 0 as *mut libc::c_char;
fc_buf = dc_strdup(filecontent);
if dc_split_armored_data(
fc_buf,
&mut fc_headerline,
ptr::null_mut(),
ptr::null_mut(),
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
&mut fc_base64,
) && !fc_headerline.is_null()
&& strcmp(
@@ -471,7 +484,7 @@ pub unsafe fn dc_normalize_setup_code(
in_0: *const libc::c_char,
) -> *mut libc::c_char {
if in_0.is_null() {
return ptr::null_mut();
return 0 as *mut libc::c_char;
}
let mut out = String::new();
let mut outlen;
@@ -499,18 +512,17 @@ pub unsafe fn dc_normalize_setup_code(
}
#[allow(non_snake_case)]
pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
let mut ok_to_continue = true;
pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: *mut dc_job_t) {
let mut current_block: u64;
let mut success: libc::c_int = 0;
let mut ongoing_allocated_here: libc::c_int = 0;
let what: libc::c_int;
if !(0 == dc_alloc_ongoing(context)) {
ongoing_allocated_here = 1;
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());
what = (*job).param.get_int(Param::Cmd).unwrap_or_default();
let param1 = CString::yolo((*job).param.get(Param::Arg).unwrap_or_default());
let _param2 = CString::yolo((*job).param.get(Param::Arg2).unwrap_or_default());
if strlen(param1.as_ptr()) == 0 {
error!(context, 0, "No Import/export dir/file given.",);
@@ -522,45 +534,185 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
} else {
if what == 1 || what == 11 {
/* before we export anything, make sure the private key exists */
if dc_ensure_secret_key_exists(context).is_err() {
if 0 == dc_ensure_secret_key_exists(context) {
error!(
context,
0,
"Import/export: Cannot create private key or private key not available.",
);
ok_to_continue = false;
current_block = 3568988166330621280;
} else {
dc_create_folder(context, &param1_s);
dc_create_folder(context, param1.as_ptr());
current_block = 4495394744059808450;
}
} else {
current_block = 4495394744059808450;
}
if ok_to_continue {
match what {
match current_block {
3568988166330621280 => {}
_ => match what {
1 => {
if 0 != export_self_keys(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
current_block = 10991094515395304355;
match current_block {
2973387206439775448 => {
if 0 == import_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
11250025114629486028 => {
if 0 == import_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
12669919903773909120 => {
if 0 == export_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
_ => {
if 0 == export_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
}
match current_block {
3568988166330621280 => {}
_ => {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
}
2 => {
if 0 != import_self_keys(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
current_block = 11250025114629486028;
match current_block {
2973387206439775448 => {
if 0 == import_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
11250025114629486028 => {
if 0 == import_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
12669919903773909120 => {
if 0 == export_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
_ => {
if 0 == export_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
}
match current_block {
3568988166330621280 => {}
_ => {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
}
11 => {
if 0 != export_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
current_block = 12669919903773909120;
match current_block {
2973387206439775448 => {
if 0 == import_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
11250025114629486028 => {
if 0 == import_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
12669919903773909120 => {
if 0 == export_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
_ => {
if 0 == export_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
}
match current_block {
3568988166330621280 => {}
_ => {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
}
12 => {
if 0 != import_backup(context, param1.as_ptr()) {
info!(context, 0, "Import/export completed.",);
success = 1
current_block = 2973387206439775448;
match current_block {
2973387206439775448 => {
if 0 == import_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
11250025114629486028 => {
if 0 == import_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
12669919903773909120 => {
if 0 == export_backup(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
_ => {
if 0 == export_self_keys(context, param1.as_ptr()) {
current_block = 3568988166330621280;
} else {
current_block = 1118134448028020070;
}
}
}
match current_block {
3568988166330621280 => {}
_ => {
info!(context, 0, "Import/export completed.",);
success = 1
}
}
}
_ => {}
}
},
}
}
}
@@ -588,10 +740,7 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
0,
"Import \"{}\" to \"{}\".",
as_str(backup_to_import),
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap())
as_str(context.get_dbfile()),
);
if 0 != dc_is_configured(context) {
@@ -599,8 +748,8 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
return 0;
}
&context.sql.close(&context);
dc_delete_file(context, context.get_dbfile().unwrap());
if dc_file_exist(context, context.get_dbfile().unwrap()) {
dc_delete_file(context, context.get_dbfile());
if 0 != dc_file_exist(context, context.get_dbfile()) {
error!(
context,
0, "Cannot import backups: Cannot delete the old file.",
@@ -608,19 +757,12 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
return 0;
}
if !dc_copy_file(
context,
as_path(backup_to_import),
context.get_dbfile().unwrap(),
) {
if 0 == dc_copy_file(context, backup_to_import, context.get_dbfile()) {
return 0;
}
/* error already logged */
/* re-open copied database file */
if !context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0)
{
if !context.sql.open(&context, as_path(context.get_dbfile()), 0) {
return 0;
}
@@ -739,20 +881,11 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
context,
0,
"Backup \"{}\" to \"{}\".",
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap()),
as_str(context.get_dbfile()),
as_str(dest_pathNfilename),
);
if dc_copy_file(
context,
context.get_dbfile().unwrap(),
as_path(dest_pathNfilename),
) {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
if !(0 == dc_copy_file(context, context.get_dbfile(), dest_pathNfilename)) {
context.sql.open(&context, as_path(context.get_dbfile()), 0);
closed = false;
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
@@ -780,13 +913,32 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
_ => {
let mut total_files_cnt = 0;
let dir = std::path::Path::new(as_str(context.get_blobdir()));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
let dir_handle = std::fs::read_dir(dir);
if dir_handle.is_err() {
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
} else {
let dir_handle = dir_handle.unwrap();
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
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) {
let dir_handle = std::fs::read_dir(dir);
if dir_handle.is_err() {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
} else {
let dir_handle = dir_handle.unwrap();
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
@@ -862,13 +1014,6 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
Ok(())
}
).unwrap();
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
}
} else {
info!(context, 0, "Backup: No files to copy.",);
@@ -890,25 +1035,16 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
}
}
}
} else {
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir())
);
};
}
}
}
}
}
if closed {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
context.sql.open(&context, as_path(context.get_dbfile()), 0);
}
if 0 != delete_dest_file {
dc_delete_file(context, as_path(dest_pathNfilename));
dc_delete_file(context, dest_pathNfilename);
}
free(dest_pathNfilename as *mut libc::c_void);
@@ -926,19 +1062,28 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
Maybe we should make the "default" key handlong also a little bit smarter
(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 suffix: *mut libc::c_char = 0 as *mut libc::c_char;
let mut path_plus_name: *mut libc::c_char = 0 as *mut libc::c_char;
let mut set_default: libc::c_int;
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut buf: *mut libc::c_char = 0 as *mut libc::c_char;
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();
let mut buf2: *mut libc::c_char = 0 as *mut libc::c_char;
// a pointer inside buf2, MUST NOT be free()'d
let mut buf2_headerline: *const libc::c_char = ptr::null_mut();
let mut buf2_headerline: *const libc::c_char = 0 as *const libc::c_char;
if !dir_name.is_null() {
let dir = std::path::Path::new(as_str(dir_name));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
let dir_handle = std::fs::read_dir(dir);
if dir_handle.is_err() {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
} else {
let dir_handle = dir_handle.unwrap();
for entry in dir_handle {
if entry.is_err() {
break;
@@ -947,7 +1092,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
free(suffix as *mut libc::c_void);
let name_f = entry.file_name();
let name_c = name_f.to_c_string().unwrap();
suffix = dc_get_filesuffix_lc(name_f.to_string_lossy());
suffix = dc_get_filesuffix_lc(name_c.as_ptr());
if suffix.is_null()
|| strcmp(suffix, b"asc\x00" as *const u8 as *const libc::c_char) != 0
{
@@ -961,7 +1106,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
);
info!(context, 0, "Checking: {}", as_str(path_plus_name));
free(buf as *mut libc::c_void);
buf = ptr::null_mut();
buf = 0 as *mut libc::c_char;
if 0 == dc_read_file(
context,
path_plus_name,
@@ -977,9 +1122,9 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
if dc_split_armored_data(
buf2,
&mut buf2_headerline,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
) && strcmp(
buf2_headerline,
b"-----BEGIN PGP PUBLIC KEY BLOCK-----\x00" as *const u8 as *const libc::c_char,
@@ -1022,13 +1167,6 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
as_str(dir_name),
);
}
} else {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
}
}
@@ -1126,7 +1264,7 @@ unsafe fn export_key_to_asc_file(
)
}
info!(context, 0, "Exporting key {}", as_str(file_name),);
dc_delete_file(context, as_path(file_name));
dc_delete_file(context, file_name);
if !key.write_asc_to_file(file_name, context) {
error!(context, 0, "Cannot write key to {}", as_str(file_name),);
} else {
@@ -1146,15 +1284,147 @@ unsafe fn export_key_to_asc_file(
mod tests {
use super::*;
use std::ffi::CStr;
use num_traits::ToPrimitive;
use crate::config::Config;
use crate::key;
use crate::test_utils::*;
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 => println!("I: {}", to_str(d2)),
Event::WARNING => println!("W: {}", to_str(d2)),
Event::ERROR => println!("E: {}", to_str(d2)),
_ => (),
}
0
}
/// Create Alice with a pre-generated keypair.
fn create_alice_keypair(ctx: &Context) {
ctx.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
.unwrap();
// The keypair was created using:
// let (public, private) = crate::pgp::dc_pgp_create_keypair("alice@example.com")
// .unwrap();
// println!("{}", public.to_base64(64));
// println!("{}", private.to_base64(64));
let public = key::Key::from_base64(
concat!(
"xsBNBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1l",
"FrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSX",
"AMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4",
"Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9",
"iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchw",
"oFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAHNEzxhbGljZUBleGFtcGxl",
"LmNvbT7CwIkEEAEIADMCGQEFAl086fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iai",
"x4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9",
"OHUl3MrXtZ7QmHyOAFvbXE/6n5Eeh+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkK",
"A8e4cJqwDOHsyAnvQXZ7WNje9+BMzcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea",
"6zjGF0/qljTdoxTtsYpv5wXYuhwbYklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6",
"GkquJN814Y+xny4xhZzGOfue6SeP12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUK",
"u5wO9FFbgDySOSlEjByGejSGuBmho0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxD",
"Fc7ATQRdPOnsAQgA5oLxXRLnyugzOmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG",
"9JzDeQql+sYXgUSxOoIayItuXtnFn7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av",
"62n18Venlm0yNKpROPcZ6M/sc4m6uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/R",
"noW+/fhmwIg08dQ5m8hQe3GEOZEeLrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q",
"4zW8vk2ztB8ngwbnqYy8zrN1DCICN1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAm",
"jxLZfVDcoObFH3Cv2GB7BEYxv86KC2Y6T74Q/wARAQABwsB2BBgBCAAgBQJdPOn4",
"AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/dXshJnoW",
"qEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJywJoupwX",
"FNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJuiCQvR9m",
"MjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6RDXIeYJf",
"qrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMlammDliPw",
"sK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKObzPqgJCGw",
"jTglkixw+aSTXw=="
),
KeyType::Public,
)
.unwrap();
let private = key::Key::from_base64(
concat!(
"xcLYBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1l",
"FrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSX",
"AMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4",
"Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9",
"iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchw",
"oFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAEACAChqzVOuErmVRqvcYtq",
"m1xt1H+ZjX20z5Sn1fhTLYAcq236AWMqJvwxCXoKlc8bt2UfB+Ls9cQb1YcVq353",
"r0QiExiDeK3YlCxqd/peXJwFYTNKFC3QcnUhtpG9oS/jWjN+BRotGbjtu6Vj3M68",
"JJAq+mHJ0/9OyrqrREvGfo7uLZt7iMGemDlrDakvrbIyZrPLgay+nZ3dEFKeOQ6F",
"FrU05jyUVdoHBy0Tqx/6VpFUX9+IHcMHL2lTJB0nynBj+XZ/G4aX3WYoo3YlixHb",
"Iu35fGFA0TChoGaGPzqcI/kg2Z+b/BryG9NM3LA2cO8iGrGXAE1nPFp91jmCrQ3V",
"WushBADERP+uojjjfdO5J+RkmcFe9mFYDdtkhN+kV+LdePjiNNtcXMBhasstio0S",
"ut0GKnE7DFRhX7mkN9w2apJ2ooeFeVVWot18eSdp6Rzh6/1Z7TmhYFJ3oUxxLbnQ",
"sWIXIec1SzqWBFJUCn3IP0mCnJktFg/uGW6yLs01r5ds52uSBQQA2LSWiTwk9tEm",
"dr9mz3tHnmrkyGiyKhKGM1Z7Rch63D5yQc1s4kUMBlyuLL2QtM/e4dtaz2JAkO8k",
"QrYCnNgJ+2roTAK3kDZgYtymjdvK3HpQNtjVo7dds5RJVb6U618phZwU5WNFAEJW",
"yyImmycGfjLv+18cW/3mq0QVZejkM78D/2kHaIeJAowtBOFY2zDrKyDRoBHaUSgj",
"5BjGoviRC5rYihWDEyYDQ6mBJQstAD0Ty3MYzyUxl6ruB/BMWnMDFq5+TqtdBzu3",
"jCtZ8OEyH8A5Kdo68Wzo/PGxzMtusOdNj9+3PBmSq4yibJxbLSrn59aVUYpGLjeG",
"Kyvm9OTKkrOGN27NEzxhbGljZUBleGFtcGxlLmNvbT7CwIkEEAEIADMCGQEFAl08",
"6fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQ",
"k6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9OHUl3MrXtZ7QmHyOAFvbXE/6n5Ee",
"h+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkKA8e4cJqwDOHsyAnvQXZ7WNje9+BM",
"zcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea6zjGF0/qljTdoxTtsYpv5wXYuhwb",
"YklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6GkquJN814Y+xny4xhZzGOfue6SeP",
"12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUKu5wO9FFbgDySOSlEjByGejSGuBmh",
"o0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxDFcfC2ARdPOnsAQgA5oLxXRLnyugz",
"OmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG9JzDeQql+sYXgUSxOoIayItuXtnF",
"n7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av62n18Venlm0yNKpROPcZ6M/sc4m6",
"uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/RnoW+/fhmwIg08dQ5m8hQe3GEOZEe",
"LrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q4zW8vk2ztB8ngwbnqYy8zrN1DCIC",
"N1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAmjxLZfVDcoObFH3Cv2GB7BEYxv86K",
"C2Y6T74Q/wARAQABAAgAhSvFEYZoj1sSrXrHDjZOrryViGjCCH9t3pmkxLDrGIdd",
"KsFyN8ORUo6KUZS745yx3yFnI9EZ1IZvm9aF+jxk2lGJFtgLvfoxFOvGckwCSy8T",
"/MCiJZkz01hWo5s2VCLJheWL/GqTKjS5wXDcm+y8Wtilh+UawycdlDsSNr/D4MZL",
"j3Chq9K03l5UIR8DcC7SavNi55R2oGOfboXsdvwOlrNZdCkZOlXDI4ZKFwbDHCtp",
"Do5FS30hnJi2TecUPZWB1CaGFWnevINd4ikugVjcAoZj/QAIvfrOCgqisF/Ylg9u",
"RMUPBapmcJUueILwd0iQqvGG0aCqtchvSmlg15/lQQQA9G1NNjNAH+NQrXvDJFJe",
"/V1U3F3pz7jCjQa69c0dxSBUeNX1pG8XXD6tSkkd4Ni1mzZGcZXOmVUM6cA9I7RH",
"95RqV+QIfnXVneCRrlCjV8m6OBlkivkESXc3nW5wtCIfw7oKg9w1xuVNUaAlbCt9",
"QVLaxXJiY7ad0f5U9XJ1+w8EAPFs+M/+GZK1wOZYBL1vo7x0gL9ZggmjC4B+viBJ",
"8Q60mqTrphYFsbXHuwKV0g9aIoZMucKyEE0QLR7imttiLEz1nD8bfEScbGy9ZG//",
"wRfyJmCVAjA0pQ6LtB93d70PSVzzJrMHgbLKrDuSd6RChl7n9BIEdVyk7LEph0Yg",
"9UsRBADm6DvpKL+P3lQ0eLTfAgcQTOqLZDYmI3PvqqSkHb1kHChqOXXs8hGOSSwK",
"Gjcd4CZeNOGWR42rZyRhVgtkt6iYviIaVAWUfme6K+sLQBCeyMlmEGtykAA+LmPB",
"f4zdyUNADfoxgZF3EKHf6I3nlVn5cdT+o/9vjdY2XAOwcls1RzaFwsB2BBgBCAAg",
"BQJdPOn4AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/",
"dXshJnoWqEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJ",
"ywJoupwXFNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJ",
"uiCQvR9mMjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6",
"RDXIeYJfqrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMl",
"ammDliPwsK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKOb",
"zPqgJCGwjTglkixw+aSTXw=="
),
KeyType::Private,
)
.unwrap();
let saved = key::dc_key_save_self_keypair(
&ctx,
&public,
&private,
"alice@example.org",
1,
&ctx.sql,
);
assert_eq!(saved, true, "Failed to save Alice's key");
}
#[test]
fn test_render_setup_file() {
let t = test_context(Some(logging_cb));
configure_alice_keypair(&t.ctx);
create_alice_keypair(&t.ctx); // Trick things to think we're configured.
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
println!("{}", &msg);
// Check some substrings, indicating things got substituted.
@@ -1186,7 +1456,7 @@ mod tests {
#[test]
fn test_render_setup_file_newline_replace() {
let t = test_context(Some(ac_setup_msg_cb));
configure_alice_keypair(&t.ctx);
create_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);
assert!(msg.contains("<p>hello<br>there</p>"));

1285
src/dc_job.rs Normal file

File diff suppressed because it is too large Load Diff

209
src/dc_jobthread.rs Normal file
View File

@@ -0,0 +1,209 @@
use std::sync::{Arc, Condvar, Mutex};
use crate::context::Context;
use crate::dc_configure::*;
use crate::imap::Imap;
use crate::x::*;
#[repr(C)]
pub struct dc_jobthread_t {
pub name: &'static str,
pub folder_config_name: &'static str,
pub imap: Imap,
pub state: Arc<(Mutex<JobState>, Condvar)>,
}
pub fn dc_jobthread_init(
name: &'static str,
folder_config_name: &'static str,
imap: Imap,
) -> dc_jobthread_t {
dc_jobthread_t {
name,
folder_config_name,
imap,
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
}
}
#[derive(Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: i32,
suspended: i32,
using_handle: i32,
}
pub unsafe fn dc_jobthread_suspend(
context: &Context,
jobthread: &dc_jobthread_t,
suspend: libc::c_int,
) {
if 0 != suspend {
info!(context, 0, "Suspending {}-thread.", jobthread.name,);
{
jobthread.state.0.lock().unwrap().suspended = 1;
}
dc_jobthread_interrupt_idle(context, jobthread);
loop {
let using_handle = jobthread.state.0.lock().unwrap().using_handle;
if using_handle == 0 {
return;
}
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
}
} else {
info!(context, 0, "Unsuspending {}-thread.", jobthread.name);
let &(ref lock, ref cvar) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
state.suspended = 0;
state.idle = true;
cvar.notify_one();
}
}
pub unsafe fn dc_jobthread_interrupt_idle(context: &Context, jobthread: &dc_jobthread_t) {
{
jobthread.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, 0, "Interrupting {}-IDLE...", jobthread.name);
jobthread.imap.interrupt_idle();
let &(ref lock, ref cvar) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
state.idle = true;
cvar.notify_one();
}
pub unsafe fn dc_jobthread_fetch(
context: &Context,
jobthread: &mut dc_jobthread_t,
use_network: libc::c_int,
) {
let start;
{
let &(ref lock, _) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.suspended {
return;
}
state.using_handle = 1;
}
if 0 != use_network {
start = clock();
if !(0 == connect_to_imap(context, jobthread)) {
info!(context, 0, "{}-fetch started...", jobthread.name);
jobthread.imap.fetch(context);
if jobthread.imap.should_reconnect() {
info!(
context,
0, "{}-fetch aborted, starting over...", jobthread.name,
);
jobthread.imap.fetch(context);
}
info!(
context,
0,
"{}-fetch done in {:.3} ms.",
jobthread.name,
clock().wrapping_sub(start) as f64 / 1000.0,
);
}
}
jobthread.state.0.lock().unwrap().using_handle = 0;
}
/* ******************************************************************************
* the typical fetch, idle, interrupt-idle
******************************************************************************/
unsafe fn connect_to_imap(context: &Context, jobthread: &dc_jobthread_t) -> libc::c_int {
if jobthread.imap.is_connected() {
return 1;
}
let mut ret_connected = dc_connect_to_configured_imap(context, &jobthread.imap);
if !(0 == ret_connected) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
jobthread.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context
.sql
.get_config(context, jobthread.folder_config_name)
{
jobthread.imap.set_watch_folder(mvbox_name);
} else {
jobthread.imap.disconnect(context);
ret_connected = 0;
}
}
ret_connected
}
pub unsafe fn dc_jobthread_idle(
context: &Context,
jobthread: &dc_jobthread_t,
use_network: libc::c_int,
) {
{
let &(ref lock, ref cvar) = &*jobthread.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.jobs_needed {
info!(
context,
0,
"{}-IDLE will not be started as it was interrupted while not ideling.",
jobthread.name,
);
state.jobs_needed = 0;
return;
}
if 0 != state.suspended {
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
state.using_handle = 1;
if 0 == use_network {
state.using_handle = 0;
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
}
connect_to_imap(context, jobthread);
info!(context, 0, "{}-IDLE started...", jobthread.name,);
jobthread.imap.idle(context);
info!(context, 0, "{}-IDLE ended.", jobthread.name);
jobthread.state.0.lock().unwrap().using_handle = 0;
}

754
src/dc_location.rs Normal file
View File

@@ -0,0 +1,754 @@
use std::ffi::CString;
use crate::constants::Event;
use crate::constants::*;
use crate::context::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_job::*;
use crate::dc_msg::*;
use crate::dc_saxparser::*;
use crate::dc_tools::*;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
// location handling
#[derive(Clone, Default)]
#[allow(non_camel_case_types)]
pub struct dc_location {
pub location_id: uint32_t,
pub latitude: libc::c_double,
pub longitude: libc::c_double,
pub accuracy: libc::c_double,
pub timestamp: i64,
pub contact_id: uint32_t,
pub msg_id: uint32_t,
pub chat_id: uint32_t,
pub marker: Option<String>,
pub independent: uint32_t,
}
impl dc_location {
pub fn new() -> Self {
dc_location {
location_id: 0,
latitude: 0.0,
longitude: 0.0,
accuracy: 0.0,
timestamp: 0,
contact_id: 0,
msg_id: 0,
chat_id: 0,
marker: None,
independent: 0,
}
}
}
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct dc_kml_t {
pub addr: *mut libc::c_char,
pub locations: Option<Vec<dc_location>>,
pub tag: libc::c_int,
pub curr: dc_location,
}
impl dc_kml_t {
pub fn new() -> Self {
dc_kml_t {
addr: std::ptr::null_mut(),
locations: None,
tag: 0,
curr: dc_location::new(),
}
}
}
// location streaming
pub unsafe fn dc_send_locations_to_chat(
context: &Context,
chat_id: uint32_t,
seconds: libc::c_int,
) {
let now = time();
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let is_sending_locations_before: bool;
if !(seconds < 0i32 || chat_id <= 9i32 as libc::c_uint) {
is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);
if sql::execute(
context,
&context.sql,
"UPDATE chats \
SET locations_send_begin=?, \
locations_send_until=? \
WHERE id=?",
params![
if 0 != seconds { now } else { 0 },
if 0 != seconds {
now + seconds as i64
} else {
0
},
chat_id as i32,
],
)
.is_ok()
{
if 0 != seconds && !is_sending_locations_before {
msg = dc_msg_new(context, Viewtype::Text);
(*msg).text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
(*msg).param.set_int(Param::Cmd, 8);
dc_send_msg(context, chat_id, msg);
} else if 0 == seconds && is_sending_locations_before {
let stock_str = CString::new(context.stock_system_msg(
StockMessage::MsgLocationDisabled,
"",
"",
0,
))
.unwrap();
dc_add_device_msg(context, chat_id, stock_str.as_ptr());
}
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
dc_job_add(
context,
5007i32,
chat_id as libc::c_int,
Params::new(),
seconds + 1i32,
);
}
}
}
dc_msg_unref(msg);
}
/*******************************************************************************
* job to send locations out to all chats that want them
******************************************************************************/
#[allow(non_snake_case)]
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
if 0 != flags & 0x1 || !dc_job_action_exists(context, 5005) {
dc_job_add(context, 5005, 0, Params::new(), 60);
};
}
pub fn dc_is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
context
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
)
.unwrap_or_default()
}
pub fn dc_set_location(
context: &Context,
latitude: libc::c_double,
longitude: libc::c_double,
accuracy: libc::c_double,
) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
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![
latitude,
longitude,
accuracy,
time(),
chat_id,
1,
]
)?;
continue_streaming = true;
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
};
unsafe { schedule_MAYBE_SEND_LOCATIONS(context, 0) };
Ok(continue_streaming as libc::c_int)
}
).unwrap_or_default()
}
pub fn dc_get_locations(
context: &Context,
chat_id: uint32_t,
contact_id: uint32_t,
timestamp_from: i64,
mut timestamp_to: i64,
) -> *mut dc_array_t {
if timestamp_to == 0 {
timestamp_to = time() + 10;
}
context
.sql
.query_map(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
m.id, l.from_id, l.chat_id, m.txt \
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
params![
if chat_id == 0 { 1 } else { 0 },
chat_id as i32,
if contact_id == 0 { 1 } else { 0 },
contact_id as i32,
timestamp_from,
timestamp_to,
],
|row| {
let msg_id = row.get(6)?;
let txt: String = row.get(9)?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = dc_location {
location_id: row.get(0)?,
latitude: row.get(1)?,
longitude: row.get(2)?,
accuracy: row.get(3)?,
timestamp: row.get(4)?,
independent: row.get(5)?,
msg_id: msg_id,
contact_id: row.get(7)?,
chat_id: row.get(8)?,
marker: marker,
};
Ok(loc)
},
|locations| {
let mut ret = Vec::new();
for location in locations {
ret.push(location?);
}
Ok(dc_array_t::from(ret).into_raw())
},
)
.unwrap_or_else(|_| std::ptr::null_mut())
}
fn is_marker(txt: &str) -> bool {
txt.len() == 1 && txt.chars().next().unwrap() != ' '
}
pub fn dc_delete_all_locations(context: &Context) -> bool {
if sql::execute(context, &context.sql, "DELETE FROM locations;", params![]).is_err() {
return false;
}
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
true
}
pub fn dc_get_location_kml(
context: &Context,
chat_id: uint32_t,
last_added_location_id: *mut uint32_t,
) -> *mut libc::c_char {
let mut success: libc::c_int = 0;
let now = time();
let mut location_count: libc::c_int = 0;
let mut ret = String::new();
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if let Ok((locations_send_begin, locations_send_until, locations_last_sent)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
params![chat_id as i32], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
}
) {
if !(locations_send_begin == 0 || now > locations_send_until) {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp\
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
params![1, locations_send_begin, locations_last_sent, 1],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
let longitude: f64 = row.get(2)?;
let accuracy: f64 = row.get(3)?;
let timestamp = unsafe { get_kml_timestamp(row.get(4)?) };
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
as_str(timestamp),
accuracy,
longitude,
latitude
);
location_count += 1;
if !last_added_location_id.is_null() {
unsafe { *last_added_location_id = location_id as u32 };
}
unsafe { free(timestamp as *mut libc::c_void) };
}
Ok(())
}
).unwrap(); // TODO: better error handling
}
}
if location_count > 0 {
ret += "</Document>\n</kml>";
success = 1;
}
if 0 != success {
unsafe { ret.strdup() }
} else {
std::ptr::null_mut()
}
}
/*******************************************************************************
* create kml-files
******************************************************************************/
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
.format("%Y-%m-%dT%H:%M:%SZ")
.to_string();
res.strdup()
}
pub unsafe fn dc_get_message_kml(
timestamp: i64,
latitude: libc::c_double,
longitude: libc::c_double,
) -> *mut libc::c_char {
let timestamp_str = get_kml_timestamp(timestamp);
let latitude_str = dc_ftoa(latitude);
let longitude_str = dc_ftoa(longitude);
let ret = dc_mprintf(
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
<Document>\n\
<Placemark>\
<Timestamp><when>%s</when></Timestamp>\
<Point><coordinates>%s,%s</coordinates></Point>\
</Placemark>\n\
</Document>\n\
</kml>\x00" as *const u8 as *const libc::c_char,
timestamp_str,
longitude_str, // reverse order!
latitude_str,
);
free(latitude_str as *mut libc::c_void);
free(longitude_str as *mut libc::c_void);
free(timestamp_str as *mut libc::c_void);
ret
}
pub fn dc_set_kml_sent_timestamp(context: &Context, chat_id: u32, timestamp: i64) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id as i32],
)
.is_ok()
}
pub fn dc_set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id as i32],
)
.is_ok()
}
pub unsafe fn dc_save_locations(
context: &Context,
chat_id: u32,
contact_id: u32,
locations_opt: &Option<Vec<dc_location>>,
independent: libc::c_int,
) -> u32 {
if chat_id <= 9 || locations_opt.is_none() {
return 0;
}
let locations = locations_opt.as_ref().unwrap();
context
.sql
.prepare2(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
"INSERT INTO locations\
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
|mut stmt_test, mut stmt_insert, conn| {
let mut newest_timestamp = 0;
let mut newest_location_id = 0;
for location in locations {
let exists =
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if 0 != independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
chat_id as i32,
location.latitude,
location.longitude,
location.accuracy,
independent,
])?;
if location.timestamp > newest_timestamp {
newest_timestamp = location.timestamp;
newest_location_id = sql::get_rowid2_with_conn(
context,
conn,
"locations",
"timestamp",
location.timestamp,
"from_id",
contact_id as i32,
);
}
}
}
Ok(newest_location_id)
},
)
.unwrap_or_default()
}
pub unsafe fn dc_kml_parse(
context: &Context,
content: *const libc::c_char,
content_bytes: size_t,
) -> dc_kml_t {
let mut kml = dc_kml_t::new();
let mut content_nullterminated: *mut libc::c_char = 0 as *mut libc::c_char;
let mut saxparser: dc_saxparser_t = dc_saxparser_t {
starttag_cb: None,
endtag_cb: None,
text_cb: None,
userdata: 0 as *mut libc::c_void,
};
if content_bytes > (1 * 1024 * 1024) {
warn!(
context,
0, "A kml-files with {} bytes is larger than reasonably expected.", content_bytes,
);
} else {
content_nullterminated = dc_null_terminate(content, content_bytes as libc::c_int);
if !content_nullterminated.is_null() {
kml.locations = Some(Vec::with_capacity(100));
dc_saxparser_init(
&mut saxparser,
&mut kml as *mut dc_kml_t as *mut libc::c_void,
);
dc_saxparser_set_tag_handler(
&mut saxparser,
Some(kml_starttag_cb),
Some(kml_endtag_cb),
);
dc_saxparser_set_text_handler(&mut saxparser, Some(kml_text_cb));
dc_saxparser_parse(&mut saxparser, content_nullterminated);
}
}
free(content_nullterminated as *mut libc::c_void);
kml
}
unsafe fn kml_text_cb(userdata: *mut libc::c_void, text: *const libc::c_char, _len: libc::c_int) {
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
if 0 != (*kml).tag & (0x4 | 0x10) {
let mut val: *mut libc::c_char = dc_strdup(text);
dc_str_replace(
&mut val,
b"\n\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_str_replace(
&mut val,
b"\r\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_str_replace(
&mut val,
b"\t\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_str_replace(
&mut val,
b" \x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
if 0 != (*kml).tag & 0x4 && strlen(val) >= 19 {
// YYYY-MM-DDTHH:MM:SSZ
// 0 4 7 10 13 16 19
let val_r = as_str(val);
match chrono::NaiveDateTime::parse_from_str(val_r, "%Y-%m-%dT%H:%M:%SZ") {
Ok(res) => {
(*kml).curr.timestamp = res.timestamp();
if (*kml).curr.timestamp > time() {
(*kml).curr.timestamp = time();
}
}
Err(_err) => {
(*kml).curr.timestamp = time();
}
}
} else if 0 != (*kml).tag & 0x10 {
let mut comma: *mut libc::c_char = strchr(val, ',' as i32);
if !comma.is_null() {
let longitude: *mut libc::c_char = val;
let latitude: *mut libc::c_char = comma.offset(1isize);
*comma = 0 as libc::c_char;
comma = strchr(latitude, ',' as i32);
if !comma.is_null() {
*comma = 0 as libc::c_char
}
(*kml).curr.latitude = dc_atof(latitude);
(*kml).curr.longitude = dc_atof(longitude)
}
}
free(val as *mut libc::c_void);
};
}
unsafe fn kml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0 {
if 0 != (*kml).tag & 0x1
&& 0 != (*kml).curr.timestamp
&& 0. != (*kml).curr.latitude
&& 0. != (*kml).curr.longitude
{
let location = (*kml).curr.clone();
((*kml).locations.as_mut().unwrap()).push(location);
}
(*kml).tag = 0
};
}
/*******************************************************************************
* parse kml-files
******************************************************************************/
unsafe fn kml_starttag_cb(
userdata: *mut libc::c_void,
tag: *const libc::c_char,
attr: *mut *mut libc::c_char,
) {
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
if strcmp(tag, b"document\x00" as *const u8 as *const libc::c_char) == 0 {
let addr: *const libc::c_char =
dc_attr_find(attr, b"addr\x00" as *const u8 as *const libc::c_char);
if !addr.is_null() {
(*kml).addr = dc_strdup(addr)
}
} else if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0 {
(*kml).tag = 0x1;
(*kml).curr.timestamp = 0;
(*kml).curr.latitude = 0 as libc::c_double;
(*kml).curr.longitude = 0.0f64;
(*kml).curr.accuracy = 0.0f64
} else if strcmp(tag, b"timestamp\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x1
{
(*kml).tag = 0x1 | 0x2
} else if strcmp(tag, b"when\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x2
{
(*kml).tag = 0x1 | 0x2 | 0x4
} else if strcmp(tag, b"point\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x1
{
(*kml).tag = 0x1 | 0x8
} else if strcmp(tag, b"coordinates\x00" as *const u8 as *const libc::c_char) == 0
&& 0 != (*kml).tag & 0x8
{
(*kml).tag = 0x1 | 0x8 | 0x10;
let accuracy: *const libc::c_char =
dc_attr_find(attr, b"accuracy\x00" as *const u8 as *const libc::c_char);
if !accuracy.is_null() {
(*kml).curr.accuracy = dc_atof(accuracy)
}
};
}
pub unsafe fn dc_kml_unref(kml: &mut dc_kml_t) {
free((*kml).addr as *mut libc::c_void);
}
#[allow(non_snake_case)]
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mut dc_job_t) {
let now = time();
let mut continue_streaming: libc::c_int = 1;
info!(
context,
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
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| {
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
continue;
}
// 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 = 1;
(*msg).param.set_int(Param::Cmd, 9);
dc_send_msg(context, chat_id as u32, msg);
dc_msg_unref(msg);
}
Ok(())
},
)
},
)
.unwrap(); // TODO: Better error handling
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
}
#[allow(non_snake_case)]
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut dc_job_t) {
// this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.
let chat_id = (*job).foreign_id;
if let Ok((send_begin, send_until)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
params![chat_id as i32],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
) {
if !(send_begin != 0 && time() <= send_until) {
// still streaming -
// may happen as several calls to dc_send_locations_to_chat()
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
if !(send_begin == 0 && send_until == 0) {
// not streaming, device-message already sent
if context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id as i32],
).is_ok() {
let stock_str = CString::new(context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)).unwrap();
dc_add_device_msg(context, chat_id, stock_str.as_ptr());
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as usize,
0,
);
}
}
}
}
}

185
src/dc_lot.rs Normal file
View File

@@ -0,0 +1,185 @@
use crate::contact::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_msg::*;
use crate::dc_tools::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
use std::ffi::CString;
use std::ptr;
/* * Structure behind dc_lot_t */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_lot_t {
pub magic: uint32_t,
pub text1_meaning: libc::c_int,
pub text1: *mut libc::c_char,
pub text2: *mut libc::c_char,
pub timestamp: i64,
pub state: libc::c_int,
pub id: uint32_t,
pub fingerprint: *mut libc::c_char,
pub invitenumber: *mut libc::c_char,
pub auth: *mut libc::c_char,
}
/* *
* @class dc_lot_t
*
* An object containing a set of values.
* The meaning of the values is defined by the function returning the object.
* Lot objects are created
* eg. by chatlist.get_summary() or dc_msg_get_summary().
*
* NB: _Lot_ is used in the meaning _heap_ here.
*/
pub unsafe fn dc_lot_new() -> *mut dc_lot_t {
let mut lot: *mut dc_lot_t;
lot = calloc(1, ::std::mem::size_of::<dc_lot_t>()) as *mut dc_lot_t;
assert!(!lot.is_null());
(*lot).magic = 0x107107i32 as uint32_t;
(*lot).text1_meaning = 0i32;
lot
}
pub unsafe fn dc_lot_empty(mut lot: *mut dc_lot_t) {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return;
}
free((*lot).text1 as *mut libc::c_void);
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32;
free((*lot).text2 as *mut libc::c_void);
(*lot).text2 = 0 as *mut libc::c_char;
free((*lot).fingerprint as *mut libc::c_void);
(*lot).fingerprint = 0 as *mut libc::c_char;
free((*lot).invitenumber as *mut libc::c_void);
(*lot).invitenumber = 0 as *mut libc::c_char;
free((*lot).auth as *mut libc::c_void);
(*lot).auth = 0 as *mut libc::c_char;
(*lot).timestamp = 0;
(*lot).state = 0i32;
(*lot).id = 0i32 as uint32_t;
}
pub unsafe fn dc_lot_unref(mut set: *mut dc_lot_t) {
if set.is_null() || (*set).magic != 0x107107i32 as libc::c_uint {
return;
}
dc_lot_empty(set);
(*set).magic = 0i32 as uint32_t;
free(set as *mut libc::c_void);
}
pub unsafe fn dc_lot_get_text1(lot: *const dc_lot_t) -> *mut libc::c_char {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0 as *mut libc::c_char;
}
dc_strdup_keep_null((*lot).text1)
}
pub unsafe fn dc_lot_get_text2(lot: *const dc_lot_t) -> *mut libc::c_char {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0 as *mut libc::c_char;
}
dc_strdup_keep_null((*lot).text2)
}
pub unsafe fn dc_lot_get_text1_meaning(lot: *const dc_lot_t) -> libc::c_int {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0i32;
}
(*lot).text1_meaning
}
pub unsafe fn dc_lot_get_state(lot: *const dc_lot_t) -> libc::c_int {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0i32;
}
(*lot).state
}
pub unsafe fn dc_lot_get_id(lot: *const dc_lot_t) -> uint32_t {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0i32 as uint32_t;
}
(*lot).id
}
pub unsafe fn dc_lot_get_timestamp(lot: *const dc_lot_t) -> i64 {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
return 0;
}
(*lot).timestamp
}
/* library-internal */
/* in practice, the user additionally cuts the string himself pixel-accurate */
pub unsafe fn dc_lot_fill(
mut lot: *mut dc_lot_t,
msg: *mut dc_msg_t,
chat: *const Chat,
contact: Option<&Contact>,
context: &Context,
) {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() {
return;
}
if (*msg).state == 19i32 {
(*lot).text1 = context.stock_str(StockMessage::Draft).strdup();
(*lot).text1_meaning = 1i32
} else if (*msg).from_id == 1i32 as libc::c_uint {
if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else {
(*lot).text1 = context.stock_str(StockMessage::SelfMsg).strdup();
(*lot).text1_meaning = 3i32
}
} else if chat.is_null() {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 {
if 0 != dc_msg_is_info(msg) || contact.is_none() {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else {
if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint {
if let Some(contact) = contact {
(*lot).text1 = contact.get_display_name().strdup();
} else {
(*lot).text1 = std::ptr::null_mut();
}
} else {
if let Some(contact) = contact {
(*lot).text1 = contact.get_first_name().strdup();
} else {
(*lot).text1 = std::ptr::null_mut();
}
}
(*lot).text1_meaning = 2i32;
}
}
let msgtext_c = (*msg)
.text
.as_ref()
.map(|s| CString::yolo(String::as_str(s)));
let msgtext_ptr = msgtext_c.map_or(ptr::null(), |s| s.as_ptr());
(*lot).text2 =
dc_msg_get_summarytext_by_raw((*msg).type_0, msgtext_ptr, &mut (*msg).param, 160, context);
(*lot).timestamp = dc_msg_get_timestamp(msg);
(*lot).state = (*msg).state;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
use crate::constants::*;
use crate::context::*;
use crate::job::*;
use crate::message::*;
use crate::dc_job::*;
use crate::dc_msg::*;
use crate::param::Params;
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
@@ -18,27 +18,23 @@ pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u3
return;
}
if let Ok(msg) = dc_msg_new_load(context, msg_id) {
if dc_msg_is_setupmessage(&msg) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
return;
}
if dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Stay);
}
// 1 = dc message, 2 = reply to dc message
if 0 != msg.is_dc_message {
job_add(
context,
Action::MoveMsg,
msg.id as libc::c_int,
Params::new(),
0,
);
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Moving);
}
let msg = dc_msg_new_load(context, msg_id);
if dc_msg_is_setupmessage(msg) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
dc_msg_unref(msg);
return;
}
if dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_STAY);
}
// 1 = dc message, 2 = reply to dc message
if 0 != (*msg).is_dc_message {
dc_job_add(context, 200, (*msg).id as libc::c_int, Params::new(), 0);
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
}
dc_msg_unref(msg);
}

1591
src/dc_msg.rs Normal file

File diff suppressed because it is too large Load Diff

346
src/dc_qr.rs Normal file
View File

@@ -0,0 +1,346 @@
use percent_encoding::percent_decode_str;
use crate::contact::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_lot::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::key::*;
use crate::param::*;
use crate::peerstate::*;
use crate::types::*;
use crate::x::*;
// out-of-band verification
// id=contact
// text1=groupname
// id=contact
// id=contact
// test1=formatted fingerprint
// id=contact
// text1=text
// text1=URL
// text1=error string
pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc_lot_t {
let mut current_block: u64;
let mut payload: *mut libc::c_char = 0 as *mut libc::c_char;
// must be normalized, if set
let mut addr: *mut libc::c_char = 0 as *mut libc::c_char;
// must be normalized, if set
let mut fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
let mut name: *mut libc::c_char = 0 as *mut libc::c_char;
let mut invitenumber: *mut libc::c_char = 0 as *mut libc::c_char;
let mut auth: *mut libc::c_char = 0 as *mut libc::c_char;
let mut qr_parsed: *mut dc_lot_t = dc_lot_new();
let mut chat_id: uint32_t = 0i32 as uint32_t;
let mut device_msg: *mut libc::c_char = 0 as *mut libc::c_char;
let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char;
let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char;
(*qr_parsed).state = 0i32;
if !qr.is_null() {
info!(context, 0, "Scanned QR code: {}", as_str(qr),);
/* split parameters from the qr code
------------------------------------ */
if strncasecmp(
qr,
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
strlen(b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
payload =
dc_strdup(&*qr.offset(strlen(
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
) as isize));
let mut fragment: *mut libc::c_char = strchr(payload, '#' as i32);
if !fragment.is_null() {
*fragment = 0i32 as libc::c_char;
fragment = fragment.offset(1isize);
let param: Params = as_str(fragment).parse().expect("invalid params");
addr = param
.get(Param::Forwarded)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
if !addr.is_null() {
if let Some(ref name_enc) = param.get(Param::SetLongitude) {
let name_r = percent_decode_str(name_enc)
.decode_utf8()
.expect("invalid name");
name = normalize_name(name_r).strdup();
}
invitenumber = param
.get(Param::ProfileImage)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
auth = param
.get(Param::Auth)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
grpid = param
.get(Param::GroupId)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
if !grpid.is_null() {
if let Some(grpname_enc) = param.get(Param::GroupName) {
let grpname_r = percent_decode_str(grpname_enc)
.decode_utf8()
.expect("invalid groupname");
grpname = grpname_r.strdup();
}
}
}
}
fingerprint = dc_normalize_fingerprint_c(payload);
current_block = 5023038348526654800;
} else if strncasecmp(
qr,
b"mailto:\x00" as *const u8 as *const libc::c_char,
strlen(b"mailto:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
payload = dc_strdup(
&*qr.offset(strlen(b"mailto:\x00" as *const u8 as *const libc::c_char) as isize),
);
let query: *mut libc::c_char = strchr(payload, '?' as i32);
if !query.is_null() {
*query = 0i32 as libc::c_char
}
addr = dc_strdup(payload);
current_block = 5023038348526654800;
} else if strncasecmp(
qr,
b"SMTP:\x00" as *const u8 as *const libc::c_char,
strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
payload = dc_strdup(
&*qr.offset(strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char) as isize),
);
let colon: *mut libc::c_char = strchr(payload, ':' as i32);
if !colon.is_null() {
*colon = 0i32 as libc::c_char
}
addr = dc_strdup(payload);
current_block = 5023038348526654800;
} else if strncasecmp(
qr,
b"MATMSG:\x00" as *const u8 as *const libc::c_char,
strlen(b"MATMSG:\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
/* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */
/* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */
let to: *mut libc::c_char = strstr(qr, b"TO:\x00" as *const u8 as *const libc::c_char);
if !to.is_null() {
addr = dc_strdup(&mut *to.offset(3isize));
let semicolon: *mut libc::c_char = strchr(addr, ';' as i32);
if !semicolon.is_null() {
*semicolon = 0i32 as libc::c_char
}
current_block = 5023038348526654800;
} else {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 =
dc_strdup(b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char);
current_block = 16562876845594826114;
}
} else {
if strncasecmp(
qr,
b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char,
strlen(b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
let lines = dc_split_into_lines(qr);
for &key in &lines {
dc_trim(key);
let mut value: *mut libc::c_char = strchr(key, ':' as i32);
if !value.is_null() {
*value = 0i32 as libc::c_char;
value = value.offset(1isize);
let mut semicolon_0: *mut libc::c_char = strchr(key, ';' as i32);
if !semicolon_0.is_null() {
*semicolon_0 = 0i32 as libc::c_char
}
if strcasecmp(key, b"EMAIL\x00" as *const u8 as *const libc::c_char) == 0i32
{
semicolon_0 = strchr(value, ';' as i32);
if !semicolon_0.is_null() {
*semicolon_0 = 0i32 as libc::c_char
}
addr = dc_strdup(value)
} else if strcasecmp(key, b"N\x00" as *const u8 as *const libc::c_char)
== 0i32
{
semicolon_0 = strchr(value, ';' as i32);
if !semicolon_0.is_null() {
semicolon_0 = strchr(semicolon_0.offset(1isize), ';' as i32);
if !semicolon_0.is_null() {
*semicolon_0 = 0i32 as libc::c_char
}
}
name = dc_strdup(value);
dc_str_replace(
&mut name,
b";\x00" as *const u8 as *const libc::c_char,
b",\x00" as *const u8 as *const libc::c_char,
);
name = normalize_name(as_str(name)).strdup();
}
}
}
dc_free_splitted_lines(lines);
}
current_block = 5023038348526654800;
}
match current_block {
16562876845594826114 => {}
_ => {
/* check the parameters
---------------------- */
if !addr.is_null() {
/* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
let mut temp: *mut libc::c_char = dc_urldecode(addr);
free(addr as *mut libc::c_void);
addr = temp;
temp = addr_normalize(as_str(addr)).strdup();
free(addr as *mut libc::c_void);
addr = temp;
if !may_be_valid_addr(as_str(addr)) {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 = dc_strdup(
b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char,
);
current_block = 16562876845594826114;
} else {
current_block = 14116432890150942211;
}
} else {
current_block = 14116432890150942211;
}
match current_block {
16562876845594826114 => {}
_ => {
if !fingerprint.is_null() {
if strlen(fingerprint) != 40 {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 = dc_strdup(
b"Bad fingerprint length in QR code.\x00" as *const u8
as *const libc::c_char,
);
current_block = 16562876845594826114;
} else {
current_block = 5409161009579131794;
}
} else {
current_block = 5409161009579131794;
}
match current_block {
16562876845594826114 => {}
_ => {
if !fingerprint.is_null() {
let peerstate = Peerstate::from_fingerprint(
context,
&context.sql,
as_str(fingerprint),
);
if addr.is_null() || invitenumber.is_null() || auth.is_null() {
if let Some(peerstate) = peerstate {
(*qr_parsed).state = 210i32;
let addr = peerstate
.addr
.as_ref()
.map(|s| s.as_str())
.unwrap_or_else(|| "");
(*qr_parsed).id = Contact::add_or_lookup(
context,
"",
addr,
Origin::UnhandledQrScan,
)
.map(|(id, _)| id)
.unwrap_or_default();
dc_create_or_lookup_nchat_by_contact_id(
context,
(*qr_parsed).id,
2i32,
&mut chat_id,
0 as *mut libc::c_int,
);
device_msg = dc_mprintf(
b"%s verified.\x00" as *const u8
as *const libc::c_char,
peerstate.addr,
)
} else {
(*qr_parsed).text1 =
dc_format_fingerprint_c(fingerprint);
(*qr_parsed).state = 230i32
}
} else {
if !grpid.is_null() && !grpname.is_null() {
(*qr_parsed).state = 202i32;
(*qr_parsed).text1 = dc_strdup(grpname);
(*qr_parsed).text2 = dc_strdup(grpid)
} else {
(*qr_parsed).state = 200i32
}
(*qr_parsed).id = Contact::add_or_lookup(
context,
as_str(name),
as_str(addr),
Origin::UnhandledQrScan,
)
.map(|(id, _)| id)
.unwrap_or_default();
(*qr_parsed).fingerprint = dc_strdup(fingerprint);
(*qr_parsed).invitenumber = dc_strdup(invitenumber);
(*qr_parsed).auth = dc_strdup(auth)
}
} else if !addr.is_null() {
(*qr_parsed).state = 320i32;
(*qr_parsed).id = Contact::add_or_lookup(
context,
as_str(name),
as_str(addr),
Origin::UnhandledQrScan,
)
.map(|(id, _)| id)
.unwrap_or_default();
} else if strstr(
qr,
b"http://\x00" as *const u8 as *const libc::c_char,
) == qr as *mut libc::c_char
|| strstr(
qr,
b"https://\x00" as *const u8 as *const libc::c_char,
) == qr as *mut libc::c_char
{
(*qr_parsed).state = 332i32;
(*qr_parsed).text1 = dc_strdup(qr)
} else {
(*qr_parsed).state = 330i32;
(*qr_parsed).text1 = dc_strdup(qr)
}
if !device_msg.is_null() {
dc_add_device_msg(context, chat_id, device_msg);
}
}
}
}
}
}
}
}
free(addr as *mut libc::c_void);
free(fingerprint as *mut libc::c_void);
free(payload as *mut libc::c_void);
free(name as *mut libc::c_void);
free(invitenumber as *mut libc::c_void);
free(auth as *mut libc::c_void);
free(device_msg as *mut libc::c_void);
free(grpname as *mut libc::c_void);
free(grpid as *mut libc::c_void);
qr_parsed
}

File diff suppressed because it is too large Load Diff

1089
src/dc_saxparser.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,65 @@
use crate::dc_dehtml::*;
use crate::dc_tools::*;
use crate::x::*;
#[derive(Copy, Clone)]
pub struct Simplify {
pub is_forwarded: bool,
#[repr(C)]
pub struct dc_simplify_t {
pub is_forwarded: libc::c_int,
pub is_cut_at_begin: libc::c_int,
pub is_cut_at_end: libc::c_int,
}
/// Return index of footer line in vector of message lines, or vector length if
/// no footer is found.
///
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
for ix in 0..lines.len() {
let line = lines[ix];
// quoted-printable may encode `-- ` to `-- =20` which is converted
// back to `-- `
match line.as_ref() {
"-- " | "-- " => return (ix, false),
"--" | "---" | "----" => return (ix, true),
_ => (),
}
}
return (lines.len(), false);
}
impl Simplify {
impl dc_simplify_t {
pub fn new() -> Self {
Simplify {
is_forwarded: false,
dc_simplify_t {
is_forwarded: 0,
is_cut_at_begin: 0,
is_cut_at_end: 0,
}
}
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
/// lineends etc.
/// The data returned from simplify() must be free()'d when no longer used.
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
let mut out = if is_html {
dc_dehtml(input)
} else {
input.to_string()
};
pub unsafe fn simplify(
&mut self,
in_unterminated: *const libc::c_char,
in_bytes: libc::c_int,
is_html: libc::c_int,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
if in_bytes <= 0 {
return "".strdup();
}
out.retain(|c| c != '\r');
out = self.simplify_plain_text(&out, is_msgrmsg);
out.retain(|c| c != '\r');
/* create a copy of the given buffer */
let mut out: *mut libc::c_char;
let mut temp: *mut libc::c_char;
self.is_forwarded = 0i32;
self.is_cut_at_begin = 0i32;
self.is_cut_at_end = 0i32;
out = strndup(
in_unterminated as *mut libc::c_char,
in_bytes as libc::c_ulong,
);
if out.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if 0 != is_html {
temp = dc_dehtml(out);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
}
}
dc_remove_cr_chars(out);
temp = self.simplify_plain_text(out, is_msgrmsg);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
}
dc_remove_cr_chars(out);
out
}
@@ -52,75 +68,102 @@ impl Simplify {
* Simplify Plain Text
*/
#[allow(non_snake_case)]
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
unsafe fn simplify_plain_text(
&mut self,
buf_terminated: *const libc::c_char,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
/* This function ...
... removes all text after the line `-- ` (footer mark)
... removes full quotes at the beginning and at the end of the text -
these are all lines starting with the character `>`
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
/* split the given buffer into lines */
let lines: Vec<_> = buf_terminated.split('\n').collect();
let lines = dc_split_into_lines(buf_terminated);
let mut l_first: usize = 0;
let mut is_cut_at_begin = false;
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
if l_last > l_first + 2 {
let line0 = lines[l_first];
let line1 = lines[l_first + 1];
let line2 = lines[l_first + 2];
if line0 == "---------- Forwarded message ----------"
&& line1.starts_with("From: ")
&& line2.is_empty()
{
self.is_forwarded = true;
l_first += 3
}
}
let mut l_last = lines.len();
let mut line: *mut libc::c_char;
let mut footer_mark: libc::c_int = 0i32;
for l in l_first..l_last {
let line = lines[l];
if line == "-----"
|| line == "_____"
|| line == "====="
|| line == "*****"
|| line == "~~~~~"
line = lines[l];
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32
}
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32;
self.is_cut_at_end = 1i32
}
if 0 != footer_mark {
l_last = l;
is_cut_at_end = true;
/* done */
break;
}
}
if !is_msgrmsg {
if l_last > l_first + 2 {
let line0: *mut libc::c_char = lines[l_first];
let line1: *mut libc::c_char = lines[l_first + 1];
let line2: *mut libc::c_char = lines[l_first + 2];
if strcmp(
line0,
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
) == 0i32
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
&& *line2.offset(0isize) as libc::c_int == 0i32
{
self.is_forwarded = 1i32;
l_first += 3
}
}
for l in l_first..l_last {
line = lines[l];
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
{
l_last = l;
self.is_cut_at_end = 1i32;
/* done */
break;
}
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine = None;
for l in (l_first..l_last).rev() {
let line = lines[l];
line = lines[l];
if is_plain_quote(line) {
l_lastQuotedLine = Some(l)
} else if !is_empty_line(line) {
break;
}
}
if let Some(last_quoted_line) = l_lastQuotedLine {
l_last = last_quoted_line;
is_cut_at_end = true;
if l_lastQuotedLine.is_some() {
l_last = l_lastQuotedLine.unwrap();
self.is_cut_at_end = 1i32;
if l_last > 1 {
if is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
}
if l_last > 1 {
let line = lines[l_last - 1];
line = lines[l_last - 1];
if is_quoted_headline(line) {
l_last -= 1
}
}
}
}
if !is_msgrmsg {
if 0 == is_msgrmsg {
let mut l_lastQuotedLine_0 = None;
let mut hasQuotedHeadline = 0;
for l in l_first..l_last {
let line = lines[l];
line = lines[l];
if is_plain_quote(line) {
l_lastQuotedLine_0 = Some(l)
} else if !is_empty_line(line) {
@@ -135,21 +178,21 @@ impl Simplify {
}
}
}
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
l_first = last_quoted_line + 1;
is_cut_at_begin = true
if l_lastQuotedLine_0.is_some() {
l_first = l_lastQuotedLine_0.unwrap() + 1;
self.is_cut_at_begin = 1i32
}
}
/* re-create buffer from the remaining lines */
let mut ret = String::new();
if is_cut_at_begin {
if 0 != self.is_cut_at_begin {
ret += "[...]";
}
/* we write empty lines only in case and non-empty line follows */
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
for l in l_first..l_last {
let line = lines[l];
line = lines[l];
if is_empty_line(line) {
pending_linebreaks += 1
} else {
@@ -163,105 +206,144 @@ impl Simplify {
}
}
// the incoming message might contain invalid UTF8
ret += line;
ret += &to_string_lossy(line);
content_lines_added += 1;
pending_linebreaks = 1i32
}
}
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
if 0 != self.is_cut_at_end && (0 == self.is_cut_at_begin || 0 != content_lines_added) {
ret += " [...]";
}
dc_free_splitted_lines(lines);
ret
ret.strdup()
}
}
/**
* Tools
*/
fn is_empty_line(buf: &str) -> bool {
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
//
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
// but having control sequences in email body?!
//
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
for c in buf.chars() {
if c > ' ' {
unsafe fn is_empty_line(buf: *const libc::c_char) -> bool {
/* force unsigned - otherwise the `> ' '` comparison will fail */
let mut p1: *const libc::c_uchar = buf as *const libc::c_uchar;
while 0 != *p1 {
if *p1 as libc::c_int > ' ' as i32 {
return false;
}
p1 = p1.offset(1isize)
}
true
}
fn is_quoted_headline(buf: &str) -> bool {
unsafe fn is_quoted_headline(buf: *const libc::c_char) -> bool {
/* This function may be called for the line _directly_ before a quote.
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
- Currently, we simply check if the last character is a ':'.
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
let buf_len: libc::c_int = strlen(buf) as libc::c_int;
if buf_len > 80i32 {
return false;
}
if buf_len > 0i32 && *buf.offset((buf_len - 1i32) as isize) as libc::c_int == ':' as i32 {
return true;
}
buf.len() <= 80 && buf.ends_with(':')
false
}
fn is_plain_quote(buf: &str) -> bool {
buf.starts_with(">")
unsafe fn is_plain_quote(buf: *const libc::c_char) -> bool {
if *buf.offset(0isize) as libc::c_int == '>' as i32 {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn test_simplify_trim() {
let mut simplify = Simplify::new();
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
let plain = simplify.simplify(html, true, false);
unsafe {
let mut simplify = dc_simplify_t::new();
let html: *const libc::c_char =
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(plain, "line1\nline2");
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
.to_str()
.unwrap(),
"line1\nline2",
);
free(plain as *mut libc::c_void);
}
}
#[test]
fn test_simplify_parse_href() {
let mut simplify = Simplify::new();
let html = "<a href=url>text</a";
let plain = simplify.simplify(html, true, false);
unsafe {
let mut simplify = dc_simplify_t::new();
let html: *const libc::c_char =
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(plain, "[text](url)");
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
.to_str()
.unwrap(),
"[text](url)",
);
free(plain as *mut libc::c_void);
}
}
#[test]
fn test_simplify_bold_text() {
let mut simplify = Simplify::new();
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
let plain = simplify.simplify(html, true, false);
unsafe {
let mut simplify = dc_simplify_t::new();
let html: *const libc::c_char =
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(plain, "text *bold*<>");
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
.to_str()
.unwrap(),
"text *bold*<>",
);
free(plain as *mut libc::c_void);
}
}
#[test]
fn test_simplify_html_encoded() {
let mut simplify = Simplify::new();
let html =
"&lt;&gt;&quot;&apos;&amp; &auml;&Auml;&ouml;&Ouml;&uuml;&Uuml;&szlig; foo&AElig;&ccedil;&Ccedil; &diams;&lrm;&rlm;&zwnj;&noent;&zwj;";
unsafe {
let mut simplify = dc_simplify_t::new();
let html: *const libc::c_char =
b"&lt;&gt;&quot;&apos;&amp; &auml;&Auml;&ouml;&Ouml;&uuml;&Uuml;&szlig; foo&AElig;&ccedil;&Ccedil; &diams;&noent;&lrm;&rlm;&zwnj;&zwj;\x00"
as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
let plain = simplify.simplify(html, true, false);
assert_eq!(
strcmp(plain,
b"<>\"\'& \xc3\xa4\xc3\x84\xc3\xb6\xc3\x96\xc3\xbc\xc3\x9c\xc3\x9f foo\xc3\x86\xc3\xa7\xc3\x87 \xe2\x99\xa6&noent;\x00"
as *const u8 as *const libc::c_char),
0,
);
assert_eq!(
plain,
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
);
}
#[test]
fn test_simplify_utilities() {
assert!(is_empty_line(" \t"));
assert!(is_empty_line(""));
assert!(is_empty_line(" \r"));
assert!(!is_empty_line(" x"));
assert!(is_plain_quote("> hello world"));
assert!(is_plain_quote(">>"));
assert!(!is_plain_quote("Life is pain"));
assert!(!is_plain_quote(""));
free(plain as *mut libc::c_void);
}
}
}

View File

@@ -1,5 +1,4 @@
use std::ffi::{CStr, CString};
use std::ptr;
use charset::Charset;
use mmime::mailmime_decode::*;
@@ -11,7 +10,7 @@ use crate::types::*;
use crate::x::*;
#[inline]
fn isalnum(c: libc::c_int) -> libc::c_int {
pub fn isalnum(c: libc::c_int) -> libc::c_int {
if c < std::u8::MAX as libc::c_int {
(c as u8 as char).is_ascii_alphanumeric() as libc::c_int
} else {
@@ -19,6 +18,48 @@ fn isalnum(c: libc::c_int) -> libc::c_int {
}
}
pub unsafe fn dc_urlencode(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut pstr: *const libc::c_char = to_encode;
if to_encode.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let buf: *mut libc::c_char =
malloc(strlen(to_encode).wrapping_mul(3).wrapping_add(1)) as *mut libc::c_char;
let mut pbuf: *mut libc::c_char = buf;
assert!(!buf.is_null());
while 0 != *pstr {
if 0 != isalnum(*pstr as libc::c_int)
|| *pstr as libc::c_int == '-' as i32
|| *pstr as libc::c_int == '_' as i32
|| *pstr as libc::c_int == '.' as i32
|| *pstr as libc::c_int == '~' as i32
{
let fresh0 = pbuf;
pbuf = pbuf.offset(1);
*fresh0 = *pstr
} else if *pstr as libc::c_int == ' ' as i32 {
let fresh1 = pbuf;
pbuf = pbuf.offset(1);
*fresh1 = '+' as i32 as libc::c_char
} else {
let fresh2 = pbuf;
pbuf = pbuf.offset(1);
*fresh2 = '%' as i32 as libc::c_char;
let fresh3 = pbuf;
pbuf = pbuf.offset(1);
*fresh3 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char);
let fresh4 = pbuf;
pbuf = pbuf.offset(1);
*fresh4 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char)
}
pstr = pstr.offset(1isize)
}
*pbuf = '\u{0}' as i32 as libc::c_char;
buf
}
/* ******************************************************************************
* URL encoding and decoding, RFC 3986
******************************************************************************/
@@ -80,7 +121,7 @@ fn hex_2_int(ch: libc::c_char) -> libc::c_char {
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur: *const libc::c_char = to_encode;
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if to_encode.is_null() || mmapstr.is_null() {
@@ -271,9 +312,9 @@ unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
if in_0.is_null() {
return ptr::null_mut();
return 0 as *mut libc::c_char;
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut out: *mut libc::c_char = 0 as *mut libc::c_char;
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,
@@ -290,8 +331,7 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
out
}
#[cfg(test)]
unsafe fn dc_encode_modified_utf7(
pub unsafe fn dc_encode_modified_utf7(
mut to_encode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
@@ -435,15 +475,13 @@ unsafe fn dc_encode_modified_utf7(
******************************************************************************/
// UTF7 modified base64 alphabet
#[cfg(test)]
static mut BASE64CHARS: [libc::c_char; 65] = [
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
];
#[cfg(test)]
unsafe fn dc_decode_modified_utf7(
pub unsafe fn dc_decode_modified_utf7(
to_decode: *const libc::c_char,
change_spaces: libc::c_int,
) -> *mut libc::c_char {
@@ -617,8 +655,8 @@ pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc:
}
pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char {
let mut decoded: *mut libc::c_char = ptr::null_mut();
let mut charset: *mut libc::c_char = ptr::null_mut();
let mut decoded: *mut libc::c_char = 0 as *mut libc::c_char;
let mut charset: *mut libc::c_char = 0 as *mut libc::c_char;
let mut p2: *const libc::c_char;
if !to_decode.is_null() {
// get char set
@@ -653,11 +691,11 @@ pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc:
}
}
free(charset as *mut libc::c_void);
if !decoded.is_null() {
return if !decoded.is_null() {
decoded
} else {
dc_strdup(to_decode)
}
};
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
@@ -672,7 +710,6 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
#[cfg(test)]
mod tests {
use super::*;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use std::ffi::CStr;
#[test]
@@ -852,15 +889,14 @@ mod tests {
#[test]
fn test_dc_urlencode_urldecode() {
unsafe {
let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC)
.to_string()
.strdup();
let buf1 =
dc_urlencode(b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char);
assert_eq!(
CStr::from_ptr(buf1 as *const libc::c_char)
.to_str()
.unwrap(),
"Bj%C3%B6rn%20Petersen"
"Bj%C3%B6rn+Petersen"
);
let buf2 = dc_urldecode(buf1);

View File

@@ -4,7 +4,7 @@ use crate::sql;
// Token namespaces
#[allow(non_camel_case_types)]
type dc_tokennamespc_t = usize;
pub type dc_tokennamespc_t = usize;
pub const DC_TOKEN_AUTH: dc_tokennamespc_t = 110;
pub const DC_TOKEN_INVITENUMBER: dc_tokennamespc_t = 100;

File diff suppressed because it is too large Load Diff

View File

@@ -18,10 +18,6 @@ pub enum Error {
Io(std::io::Error),
#[fail(display = "{:?}", _0)]
Message(String),
#[fail(display = "{:?}", _0)]
Image(image_meta::ImageError),
#[fail(display = "{:?}", _0)]
Utf8(std::str::Utf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -50,18 +46,6 @@ impl From<std::io::Error> for Error {
}
}
impl From<std::str::Utf8Error> for Error {
fn from(err: std::str::Utf8Error) -> Error {
Error::Utf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {

View File

@@ -1,9 +1,6 @@
use std::ffi::CString;
use std::net;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex, RwLock,
};
use std::sync::{Arc, Condvar, Mutex, RwLock};
use std::time::{Duration, SystemTime};
use crate::constants::*;
@@ -13,18 +10,19 @@ use crate::dc_tools::CStringExt;
use crate::oauth2::dc_get_oauth2_access_token;
use crate::types::*;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
pub const DC_IMAP_SEEN: usize = 0x0001;
pub const DC_REGENERATE: usize = 0x01;
const DC_SUCCESS: usize = 3;
const DC_ALREADY_DONE: usize = 2;
const DC_RETRY_LATER: usize = 1;
const DC_FAILED: usize = 0;
pub const DC_SUCCESS: usize = 3;
pub const DC_ALREADY_DONE: usize = 2;
pub const DC_RETRY_LATER: usize = 1;
pub const DC_FAILED: usize = 0;
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &str = "(FLAGS)";
const PREFETCH_FLAGS: &'static str = "(UID ENVELOPE)";
const BODY_FLAGS: &'static str = "(FLAGS BODY.PEEK[])";
const FETCH_FLAGS: &'static str = "(FLAGS)";
#[repr(C)]
pub struct Imap {
config: Arc<RwLock<ImapConfig>>,
watch: Arc<(Mutex<bool>, Condvar)>,
@@ -37,8 +35,6 @@ pub struct Imap {
session: Arc<Mutex<Option<Session>>>,
stream: Arc<RwLock<Option<net::TcpStream>>>,
connected: Arc<Mutex<bool>>,
should_reconnect: AtomicBool,
}
struct OAuth2 {
@@ -59,13 +55,13 @@ impl imap::Authenticator for OAuth2 {
}
#[derive(Debug)]
enum FolderMeaning {
pub enum FolderMeaning {
Unknown,
SentObjects,
Other,
}
enum Client {
pub enum Client {
Secure(
imap::Client<native_tls::TlsStream<net::TcpStream>>,
net::TcpStream,
@@ -73,12 +69,12 @@ enum Client {
Insecure(imap::Client<net::TcpStream>, net::TcpStream),
}
enum Session {
pub enum Session {
Secure(imap::Session<native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::Session<net::TcpStream>),
}
enum IdleHandle<'a> {
pub enum IdleHandle<'a> {
Secure(imap::extensions::idle::Handle<'a, native_tls::TlsStream<net::TcpStream>>),
Insecure(imap::extensions::idle::Handle<'a, net::TcpStream>),
}
@@ -203,8 +199,8 @@ impl Session {
pub fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> imap::error::Result<()> {
match self {
Session::Secure(i) => i.create(mailbox_name),
Session::Insecure(i) => i.create(mailbox_name),
Session::Secure(i) => i.subscribe(mailbox_name),
Session::Insecure(i) => i.subscribe(mailbox_name),
}
}
@@ -264,8 +260,8 @@ impl Session {
pub fn idle(&mut self) -> imap::error::Result<IdleHandle> {
match self {
Session::Secure(i) => i.idle().map(IdleHandle::Secure),
Session::Insecure(i) => i.idle().map(IdleHandle::Insecure),
Session::Secure(i) => i.idle().map(|h| IdleHandle::Secure(h)),
Session::Insecure(i) => i.idle().map(|h| IdleHandle::Insecure(h)),
}
}
@@ -307,7 +303,7 @@ impl Session {
}
}
struct ImapConfig {
pub struct ImapConfig {
pub addr: String,
pub imap_server: String,
pub imap_port: u16,
@@ -317,6 +313,7 @@ struct ImapConfig {
pub selected_folder: Option<String>,
pub selected_mailbox: Option<imap::types::Mailbox>,
pub selected_folder_needs_expunge: bool,
pub should_reconnect: bool,
pub can_idle: bool,
pub has_xlist: bool,
pub imap_delimiter: char,
@@ -335,6 +332,7 @@ impl Default for ImapConfig {
selected_folder: None,
selected_mailbox: None,
selected_folder_needs_expunge: false,
should_reconnect: false,
can_idle: false,
has_xlist: false,
imap_delimiter: '.',
@@ -362,7 +360,6 @@ impl Imap {
precheck_imf,
receive_imf,
connected: Arc::new(Mutex::new(false)),
should_reconnect: AtomicBool::new(false),
}
}
@@ -371,7 +368,7 @@ impl Imap {
}
pub fn should_reconnect(&self) -> bool {
self.should_reconnect.load(Ordering::Relaxed)
self.config.read().unwrap().should_reconnect
}
fn setup_handle_if_needed(&self, context: &Context) -> bool {
@@ -384,7 +381,7 @@ impl Imap {
}
if self.is_connected() && self.stream.read().unwrap().is_some() {
self.should_reconnect.store(false, Ordering::Relaxed);
self.config.write().unwrap().should_reconnect = false;
return true;
}
@@ -454,7 +451,7 @@ impl Imap {
}
};
self.should_reconnect.store(false, Ordering::Relaxed);
self.config.write().unwrap().should_reconnect = false;
match login_res {
Ok((session, stream)) => {
@@ -479,19 +476,25 @@ impl Imap {
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) {
eprintln!("failed to shutdown connection: {:?}", err);
if stream.is_some() {
match stream.unwrap().shutdown(net::Shutdown::Both) {
Ok(_) => {}
Err(err) => {
eprintln!("failed to shutdown connection: {:?}", err);
}
}
}
info!(
context,
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() {
eprintln!("failed to close connection: {:?}", err);
let session = self.session.lock().unwrap().take();
if session.is_some() {
match session.unwrap().close() {
Ok(_) => {}
Err(err) => {
eprintln!("failed to close connection: {:?}", err);
}
}
}
@@ -536,7 +539,7 @@ impl Imap {
let mut config = self.config.write().unwrap();
config.addr = addr.to_string();
config.imap_server = imap_server.to_string();
config.imap_port = imap_port;
config.imap_port = imap_port.into();
config.imap_user = imap_user.to_string();
config.imap_pw = imap_pw.to_string();
config.server_flags = server_flags;
@@ -547,12 +550,14 @@ impl Imap {
return false;
}
let (teardown, can_idle, has_xlist) = match &mut *self.session.lock().unwrap() {
let teardown: bool;
match &mut *self.session.lock().unwrap() {
Some(ref mut session) => {
if let Ok(caps) = session.capabilities() {
if !context.sql.is_open() {
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
(true, false, false)
teardown = true;
} else {
let can_idle = caps.has("IDLE");
let has_xlist = caps.has("XLIST");
@@ -569,23 +574,24 @@ impl Imap {
lp.mail_user,
caps_list,
);
(false, can_idle, has_xlist)
self.config.write().unwrap().can_idle = can_idle;
self.config.write().unwrap().has_xlist = has_xlist;
*self.connected.lock().unwrap() = true;
teardown = false;
}
} else {
(true, false, false)
teardown = true;
}
}
None => (true, false, false),
};
None => {
teardown = true;
}
}
if teardown {
self.unsetup_handle(context);
self.free_connect_params();
false
} else {
self.config.write().unwrap().can_idle = can_idle;
self.config.write().unwrap().has_xlist = has_xlist;
*self.connected.lock().unwrap() = true;
true
}
}
@@ -683,8 +689,9 @@ impl Imap {
err
);
self.config.write().unwrap().selected_folder = None;
self.should_reconnect.store(true, Ordering::Relaxed);
let mut config = self.config.write().unwrap();
config.selected_folder = None;
config.should_reconnect = true;
return 0;
}
}
@@ -770,7 +777,7 @@ impl Imap {
match session.fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(_err) => {
self.should_reconnect.store(true, Ordering::Relaxed);
self.config.write().unwrap().should_reconnect = true;
info!(
context,
0,
@@ -815,7 +822,7 @@ impl Imap {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(list) => list,
Err(err) => {
warn!(context, 0, "failed to fetch uids: {}", err);
eprintln!("fetch err: {:?}", err);
return 0;
}
}
@@ -921,13 +928,15 @@ impl Imap {
return 0;
}
let mut retry_later = false;
let set = format!("{}", server_uid);
let msgs = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, BODY_FLAGS) {
Ok(msgs) => msgs,
Err(err) => {
self.should_reconnect.store(true, Ordering::Relaxed);
self.config.write().unwrap().should_reconnect = true;
warn!(
context,
0,
@@ -937,11 +946,17 @@ impl Imap {
self.should_reconnect(),
err
);
return 0;
if self.should_reconnect() {
// maybe we should also retry on other errors, however, we should check this carefully, as this may result in a dead lock!
retry_later = true;
}
return if retry_later { 0 } else { 1 };
}
}
} else {
return 1;
return if retry_later { 0 } else { 1 };
};
if msgs.is_empty() {
@@ -989,7 +1004,11 @@ impl Imap {
}
}
1
if retry_later {
0
} else {
1
}
}
pub fn idle(&self, context: &Context) {
@@ -1054,7 +1073,7 @@ impl Imap {
context,
0, "IMAP-IDLE wait cancelled, we will reconnect soon."
);
self.should_reconnect.store(true, Ordering::Relaxed);
self.config.write().unwrap().should_reconnect = true;
}
_ => {
warn!(context, 0, "IMAP-IDLE returns unknown value: {}", err);
@@ -1557,23 +1576,20 @@ impl Imap {
info!(context, 0, "MVBOX-folder created.",);
}
Err(err) => {
eprintln!("create error: {:?}", err);
warn!(
context,
0,
"Cannot create MVBOX-folder, using trying INBOX subfolder. ({})",
err
0, "Cannot create MVBOX-folder, using trying INBOX subfolder."
);
match session.create(&fallback_folder) {
Ok(_) => {
mvbox_folder = Some(fallback_folder);
info!(
context,
0, "MVBOX-folder created as INBOX subfolder. ({})", err
);
info!(context, 0, "MVBOX-folder created as INBOX subfolder.",);
}
Err(err) => {
warn!(context, 0, "Cannot create MVBOX-folder. ({})", err);
eprintln!("create error: {:?}", err);
warn!(context, 0, "Cannot create MVBOX-folder.",);
}
}
}

1131
src/job.rs

File diff suppressed because it is too large Load Diff

View File

@@ -1,181 +0,0 @@
use std::sync::{Arc, Condvar, Mutex};
use crate::configure::*;
use crate::context::Context;
use crate::imap::Imap;
pub struct JobThread {
pub name: &'static str,
pub folder_config_name: &'static str,
pub imap: Imap,
pub state: Arc<(Mutex<JobState>, Condvar)>,
}
#[derive(Clone, Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: i32,
suspended: bool,
using_handle: bool,
}
impl JobThread {
pub fn new(name: &'static str, folder_config_name: &'static str, imap: Imap) -> Self {
JobThread {
name,
folder_config_name,
imap,
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
}
}
pub fn suspend(&self, context: &Context) {
info!(context, 0, "Suspending {}-thread.", self.name,);
{
self.state.0.lock().unwrap().suspended = true;
}
self.interrupt_idle(context);
loop {
let using_handle = self.state.0.lock().unwrap().using_handle;
if !using_handle {
return;
}
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
}
}
pub fn unsuspend(&self, context: &Context) {
info!(context, 0, "Unsuspending {}-thread.", self.name);
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
state.suspended = false;
state.idle = true;
cvar.notify_one();
}
pub fn interrupt_idle(&self, context: &Context) {
{
self.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, 0, "Interrupting {}-IDLE...", self.name);
self.imap.interrupt_idle();
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
state.idle = true;
cvar.notify_one();
}
pub fn fetch(&mut self, context: &Context, use_network: bool) {
{
let &(ref lock, _) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if state.suspended {
return;
}
state.using_handle = true;
}
if use_network {
let start = std::time::Instant::now();
if self.connect_to_imap(context) {
info!(context, 0, "{}-fetch started...", self.name);
self.imap.fetch(context);
if self.imap.should_reconnect() {
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(),
);
}
}
self.state.0.lock().unwrap().using_handle = false;
}
fn connect_to_imap(&self, context: &Context) -> bool {
if self.imap.is_connected() {
return true;
}
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
if ret_connected {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
self.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context.sql.get_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);
ret_connected = false;
}
}
ret_connected
}
pub fn idle(&self, context: &Context, use_network: bool) {
{
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.jobs_needed {
info!(
context,
0,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
state.jobs_needed = 0;
return;
}
if state.suspended {
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
state.using_handle = true;
if !use_network {
state.using_handle = false;
while !state.idle {
state = cvar.wait(state).unwrap();
}
state.idle = false;
return;
}
}
self.connect_to_imap(context);
info!(context, 0, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, 0, "{}-IDLE ended.", self.name);
self.state.0.lock().unwrap().using_handle = false;
}
}

View File

@@ -89,7 +89,7 @@ impl Key {
}
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option<Self> {
if bytes.is_empty() {
if 0 == bytes.len() {
return None;
}
let res: Result<Key, _> = match key_type {
@@ -283,7 +283,7 @@ impl Key {
Key::Public(_) => None,
Key::Secret(k) => {
let pub_key = k.public_key();
pub_key.sign(k, || "".into()).map(Key::Public).ok()
pub_key.sign(k, || "".into()).map(|k| Key::Public(k)).ok()
}
}
}

View File

@@ -1,9 +1,4 @@
#![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.
#![allow(clippy::unreadable_literal)]
#![feature(ptr_wrapping_offset_from)]
#![feature(c_variadic, ptr_wrapping_offset_from, ptr_cast)]
#[macro_use]
extern crate failure_derive;
@@ -22,47 +17,50 @@ mod log;
#[macro_use]
pub mod error;
mod aheader;
pub mod chat;
pub mod aheader;
pub mod chatlist;
pub mod config;
pub mod configure;
pub mod constants;
pub mod contact;
pub mod context;
mod imap;
pub mod job;
mod job_thread;
pub mod imap;
pub mod key;
pub mod keyring;
pub mod location;
pub mod lot;
pub mod message;
pub mod oauth2;
mod param;
pub mod param;
pub mod peerstate;
pub mod pgp;
pub mod qr;
mod smtp;
pub mod smtp;
pub mod sql;
mod stock;
pub mod stock;
pub mod types;
pub mod x;
pub mod dc_array;
mod dc_dehtml;
mod dc_e2ee;
pub mod dc_chat;
pub mod dc_configure;
pub mod dc_dehtml;
pub mod dc_e2ee;
pub mod dc_imex;
mod dc_loginparam;
mod dc_mimefactory;
pub mod dc_job;
pub mod dc_jobthread;
pub mod dc_location;
pub mod dc_loginparam;
pub mod dc_lot;
pub mod dc_mimefactory;
pub mod dc_mimeparser;
mod dc_move;
pub mod dc_move;
pub mod dc_msg;
pub mod dc_qr;
pub mod dc_receive_imf;
pub mod dc_saxparser;
pub mod dc_securejoin;
mod dc_simplify;
mod dc_strencode;
mod dc_token;
pub mod dc_simplify;
pub mod dc_strencode;
pub mod dc_token;
pub mod dc_tools;
pub use self::constants::*;
#[cfg(test)]
mod test_utils;
pub mod test_utils;

View File

@@ -1,696 +0,0 @@
use bitflags::bitflags;
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::job::*;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
// location handling
#[derive(Debug, Clone, Default)]
pub struct Location {
pub location_id: u32,
pub latitude: f64,
pub longitude: f64,
pub accuracy: f64,
pub timestamp: i64,
pub contact_id: u32,
pub msg_id: u32,
pub chat_id: u32,
pub marker: Option<String>,
pub independent: u32,
}
impl Location {
pub fn new() -> Self {
Default::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct Kml {
pub addr: Option<String>,
pub locations: Vec<Location>,
tag: KmlTag,
pub curr: Location,
}
bitflags! {
#[derive(Default)]
struct KmlTag: i32 {
const UNDEFINED = 0x00;
const PLACEMARK = 0x01;
const TIMESTAMP = 0x02;
const WHEN = 0x04;
const POINT = 0x08;
const COORDINATES = 0x10;
}
}
impl Kml {
pub fn new() -> Self {
Default::default()
}
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
ensure!(
content.as_ref().len() <= (1 * 1024 * 1024),
"A kml-files with {} bytes is larger than reasonably expected.",
content.as_ref().len()
);
let mut reader = quick_xml::Reader::from_str(content.as_ref());
reader.trim_text(true);
let mut kml = Kml::new();
kml.locations = Vec::with_capacity(100);
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => kml.starttag_cb(e, &reader),
Ok(quick_xml::events::Event::End(ref e)) => kml.endtag_cb(e),
Ok(quick_xml::events::Event::Text(ref e)) => kml.text_cb(e, &reader),
Err(e) => {
error!(
context,
0,
"Location parsing: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
Ok(kml)
}
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let val = val
.replace("\n", "")
.replace("\r", "")
.replace("\t", "")
.replace(" ", "");
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
// YYYY-MM-DDTHH:MM:SSZ
// 0 4 7 10 13 16 19
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
Ok(res) => {
self.curr.timestamp = res.timestamp();
if self.curr.timestamp > time() {
self.curr.timestamp = time();
}
}
Err(_err) => {
self.curr.timestamp = time();
}
}
} else if self.tag.contains(KmlTag::COORDINATES) {
let parts = val.splitn(2, ',').collect::<Vec<_>>();
if parts.len() == 2 {
self.curr.longitude = parts[0].parse().unwrap_or_default();
self.curr.latitude = parts[1].parse().unwrap_or_default();
}
}
}
}
fn endtag_cb(&mut self, event: &BytesEnd) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "placemark" {
if self.tag.contains(KmlTag::PLACEMARK)
&& 0 != self.curr.timestamp
&& 0. != self.curr.latitude
&& 0. != self.curr.longitude
{
self.locations
.push(std::mem::replace(&mut self.curr, Location::new()));
}
self.tag = KmlTag::UNDEFINED;
};
}
fn starttag_cb<B: std::io::BufRead>(
&mut self,
event: &BytesStart,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "document" {
if let Some(addr) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
.unwrap_or_default()
}) {
self.addr = addr.unwrap().unescape_and_decode_value(reader).ok();
}
} else if tag == "placemark" {
self.tag = KmlTag::PLACEMARK;
self.curr.timestamp = 0;
self.curr.latitude = 0.0;
self.curr.longitude = 0.0;
self.curr.accuracy = 0.0
} else if tag == "timestamp" && self.tag.contains(KmlTag::PLACEMARK) {
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP
} else if tag == "when" && self.tag.contains(KmlTag::TIMESTAMP) {
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP | KmlTag::WHEN
} else if tag == "point" && self.tag.contains(KmlTag::PLACEMARK) {
self.tag = KmlTag::PLACEMARK | KmlTag::POINT
} else if tag == "coordinates" && self.tag.contains(KmlTag::POINT) {
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
if let Some(acc) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
.unwrap_or_default()
}) {
let v = acc
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default();
self.curr.accuracy = v.trim().parse().unwrap_or_default();
}
}
}
}
// location streaming
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
let now = time();
let mut msg: Message;
let is_sending_locations_before: bool;
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
if sql::execute(
context,
&context.sql,
"UPDATE chats \
SET locations_send_begin=?, \
locations_send_until=? \
WHERE id=?",
params![
if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 },
chat_id as i32,
],
)
.is_ok()
{
if 0 != seconds && !is_sending_locations_before {
msg = dc_msg_new(context, Viewtype::Text);
msg.text =
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
msg.param.set_int(Param::Cmd, 8);
unsafe { chat::send_msg(context, chat_id, &mut msg).unwrap() };
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
}
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
job_add(
context,
Action::MaybeSendLocationsEnded,
chat_id as libc::c_int,
Params::new(),
seconds + 1,
);
}
}
}
}
#[allow(non_snake_case)]
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
};
}
pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
context
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
)
.unwrap_or_default()
}
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
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![
latitude,
longitude,
accuracy,
time(),
chat_id,
1,
]
)?;
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)
}
).unwrap_or_default()
}
pub fn get_range(
context: &Context,
chat_id: u32,
contact_id: u32,
timestamp_from: i64,
mut timestamp_to: i64,
) -> Vec<Location> {
if timestamp_to == 0 {
timestamp_to = time() + 10;
}
context
.sql
.query_map(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
m.id, l.from_id, l.chat_id, m.txt \
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
params![
if chat_id == 0 { 1 } else { 0 },
chat_id as i32,
if contact_id == 0 { 1 } else { 0 },
contact_id as i32,
timestamp_from,
timestamp_to,
],
|row| {
let msg_id = row.get(6)?;
let txt: String = row.get(9)?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = Location {
location_id: row.get(0)?,
latitude: row.get(1)?,
longitude: row.get(2)?,
accuracy: row.get(3)?,
timestamp: row.get(4)?,
independent: row.get(5)?,
msg_id,
contact_id: row.get(7)?,
chat_id: row.get(8)?,
marker,
};
Ok(loc)
},
|locations| {
let mut ret = Vec::new();
for location in locations {
ret.push(location?);
}
Ok(ret)
},
)
.unwrap_or_default()
}
fn is_marker(txt: &str) -> bool {
txt.len() == 1 && txt.chars().next().unwrap() != ' '
}
pub fn delete_all(context: &Context) -> Result<(), Error> {
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
Ok(())
}
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
let now = time();
let mut location_count = 0;
let mut ret = String::new();
let mut last_added_location_id = 0;
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
params![chat_id as i32], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
})?;
if !(locations_send_begin == 0 || now > locations_send_until) {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp\
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
params![1, locations_send_begin, locations_last_sent, 1],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
let longitude: f64 = row.get(2)?;
let accuracy: f64 = row.get(3)?;
let timestamp = get_kml_timestamp(row.get(4)?);
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
timestamp,
accuracy,
longitude,
latitude
);
location_count += 1;
last_added_location_id = location_id as u32;
}
Ok(())
}
)?;
}
ensure!(location_count > 0, "No locations processed");
ret += "</Document>\n</kml>";
Ok((ret, last_added_location_id))
}
fn get_kml_timestamp(utc: i64) -> String {
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
chrono::NaiveDateTime::from_timestamp(utc, 0)
.format("%Y-%m-%dT%H:%M:%SZ")
.to_string()
}
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
<Document>\n\
<Placemark>\
<Timestamp><when>{}</when></Timestamp>\
<Point><coordinates>{:.2},{:.2}</coordinates></Point>\
</Placemark>\n\
</Document>\n\
</kml>",
get_kml_timestamp(timestamp),
longitude,
latitude,
)
}
pub fn set_kml_sent_timestamp(
context: &Context,
chat_id: u32,
timestamp: i64,
) -> Result<(), Error> {
sql::execute(
context,
&context.sql,
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id as i32],
)?;
Ok(())
}
pub fn set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> Result<(), Error> {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id as i32],
)?;
Ok(())
}
pub fn save(
context: &Context,
chat_id: u32,
contact_id: u32,
locations: &[Location],
independent: i32,
) -> Result<u32, Error> {
ensure!(chat_id > 9, "Invalid chat id");
context.sql.prepare2(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
"INSERT INTO locations\
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
|mut stmt_test, mut stmt_insert, conn| {
let mut newest_timestamp = 0;
let mut newest_location_id = 0;
for location in locations {
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if 0 != independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
chat_id as i32,
location.latitude,
location.longitude,
location.accuracy,
independent,
])?;
if location.timestamp > newest_timestamp {
newest_timestamp = location.timestamp;
newest_location_id = sql::get_rowid2_with_conn(
context,
conn,
"locations",
"timestamp",
location.timestamp,
"from_id",
contact_id as i32,
);
}
}
}
Ok(newest_location_id)
},
)
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
let now = time();
let mut continue_streaming: libc::c_int = 1;
info!(
context,
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
);
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| {
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
continue;
}
// 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
unsafe { chat::send_msg(context, chat_id as u32, &mut msg).unwrap() };
}
Ok(())
},
)
},
)
.unwrap(); // TODO: Better error handling
if 0 != continue_streaming {
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
}
}
#[allow(non_snake_case)]
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
// this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done.
let chat_id = job.foreign_id;
if let Ok((send_begin, send_until)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
params![chat_id as i32],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
) {
if !(send_begin != 0 && time() <= send_until) {
// still streaming -
// may happen as several calls to dc_send_locations_to_chat()
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
if !(send_begin == 0 && send_until == 0) {
// not streaming, device-message already sent
if context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id as i32],
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_device_msg(context, chat_id, stock_str);
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as usize,
0,
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::dummy_context;
#[test]
fn test_kml_parse() {
let context = dummy_context();
let xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
assert!(kml.addr.is_some());
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);
let locations_ref = &kml.locations;
assert_eq!(locations_ref.len(), 2);
assert!(locations_ref[0].latitude > 53.6f64);
assert!(locations_ref[0].latitude < 53.8f64);
assert!(locations_ref[0].longitude > 9.3f64);
assert!(locations_ref[0].longitude < 9.5f64);
assert!(locations_ref[0].accuracy > 31.9f64);
assert!(locations_ref[0].accuracy < 32.1f64);
assert_eq!(locations_ref[0].timestamp, 1551906597);
assert!(locations_ref[1].latitude > 63.6f64);
assert!(locations_ref[1].latitude < 63.8f64);
assert!(locations_ref[1].longitude > 19.3f64);
assert!(locations_ref[1].longitude < 19.5f64);
assert!(locations_ref[1].accuracy > 2.4f64);
assert!(locations_ref[1].accuracy < 2.6f64);
assert_eq!(locations_ref[1].timestamp, 1544739072);
}
}

View File

@@ -1,109 +0,0 @@
use deltachat_derive::{FromSql, ToSql};
/// An object containing a set of values.
/// The meaning of the values is defined by the function returning the object.
/// Lot objects are created
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
///
/// _Lot_ is used in the meaning _heap_ here.
#[derive(Default, Debug, Clone)]
pub struct Lot {
pub(crate) text1_meaning: Meaning,
pub(crate) text1: Option<String>,
pub(crate) text2: Option<String>,
pub(crate) timestamp: i64,
pub(crate) state: LotState,
pub(crate) id: u32,
pub(crate) fingerprint: Option<String>,
pub(crate) invitenumber: Option<String>,
pub(crate) auth: Option<String>,
}
#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum Meaning {
None = 0,
Text1Draft = 1,
Text1Username = 2,
Text1Self = 3,
}
impl Default for Meaning {
fn default() -> Self {
Meaning::None
}
}
impl Lot {
pub fn new() -> Self {
Default::default()
}
pub fn get_text1(&self) -> Option<&str> {
self.text1.as_ref().map(|s| s.as_str())
}
pub fn get_text2(&self) -> Option<&str> {
self.text2.as_ref().map(|s| s.as_str())
}
pub fn get_text1_meaning(&self) -> Meaning {
self.text1_meaning
}
pub fn get_state(&self) -> LotState {
self.state
}
pub fn get_id(&self) -> u32 {
self.id
}
pub fn get_timestamp(&self) -> i64 {
self.timestamp
}
}
#[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum LotState {
// Default
Undefined = 0,
// Qr States
/// id=contact
QrAskVerifyContact = 200,
/// text1=groupname
QrAskVerifyGroup = 202,
/// id=contact
QrFprOk = 210,
/// id=contact
QrFprMissmatch = 220,
/// test1=formatted fingerprint
QrFprWithoutAddr = 230,
/// id=contact
QrAddr = 320,
/// text1=text
QrText = 330,
/// text1=URL
QrUrl = 332,
/// text1=error string
QrError = 400,
// Message States
MsgInFresh = 10,
MsgInNoticed = 13,
MsgInSeen = 16,
MsgOutPreparing = 18,
MsgOutDraft = 19,
MsgOutPending = 20,
MsgOutFailed = 24,
MsgOutDelivered = 26,
MsgOutMdnRcvd = 28,
}
impl Default for LotState {
fn default() -> Self {
LotState::Undefined
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ const OAUTH2_YANDEX: Oauth2 = Oauth2 {
};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Oauth2 {
pub struct Oauth2 {
client_id: &'static str,
get_code: &'static str,
init_token: &'static str,

View File

@@ -113,7 +113,7 @@ impl str::FromStr for Params {
continue;
}
// TODO: probably nicer using a regex
ensure!(pair.len() > 1, "Invalid key pair: '{}'", pair);
ensure!(pair.len() > 2, "Invalid key pair: '{}'", pair);
let mut split = pair.splitn(2, '=');
let key = split.next();
let value = split.next();
@@ -235,12 +235,4 @@ mod tests {
assert!(p1.is_empty());
assert_eq!(p1.len(), 0)
}
#[test]
fn test_regression() {
let p1: Params = "a=cli%40deltachat.de\nn=\ni=TbnwJ6lSvD5\ns=0ejvbdFSQxB"
.parse()
.unwrap();
assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de");
}
}

View File

@@ -4,9 +4,9 @@ use std::fmt;
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::chat::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::key::*;
use crate::sql::{self, Sql};
@@ -85,7 +85,7 @@ pub enum DegradeEvent {
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum VerifiedKey {
pub enum VerifiedKey {
Gossip,
Public,
None,
@@ -166,6 +166,7 @@ impl<'a> Peerstate<'a> {
pub fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option<Self> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;";
Self::from_stmt(context, query, &[addr])
}
@@ -190,11 +191,6 @@ impl<'a> Peerstate<'a> {
context
.sql
.query_row(query, params, |row| {
/* all the above queries start with this: SELECT
addr, last_seen, last_seen_autocrypt, prefer_encrypted,
public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
gossip_key_fingerprint, verified_key, verified_key_fingerprint
*/
let mut res = Self::new(context);
res.addr = Some(row.get(0)?);
@@ -202,34 +198,13 @@ impl<'a> Peerstate<'a> {
res.last_seen_autocrypt = row.get(2)?;
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
res.gossip_timestamp = row.get(5)?;
let pkf: String = row.get(7)?;
res.public_key_fingerprint = if pkf.is_empty() { None } else { Some(pkf) };
let gkf: String = row.get(8)?;
res.gossip_key_fingerprint = if gkf.is_empty() { None } else { Some(gkf) };
let vkf: String = row.get(10)?;
res.verified_key_fingerprint = if vkf.is_empty() { None } else { Some(vkf) };
res.public_key_fingerprint = row.get(7)?;
if res
.public_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.public_key_fingerprint = None;
}
res.gossip_key_fingerprint = row.get(8)?;
if res
.gossip_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.gossip_key_fingerprint = None;
}
res.verified_key_fingerprint = row.get(10)?;
if res
.verified_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.verified_key_fingerprint = None;
}
res.public_key = row
.get(4)
.ok()
@@ -242,8 +217,7 @@ impl<'a> Peerstate<'a> {
.get(9)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
res.verified_key = if vk == res.gossip_key && res.gossip_key.is_some() {
res.verified_key = if vk == res.gossip_key {
VerifiedKey::Gossip
} else if vk == res.public_key {
VerifiedKey::Public
@@ -448,7 +422,6 @@ impl<'a> Peerstate<'a> {
&self.addr,
],
).is_ok();
assert_eq!(success, true);
} else if self.to_save == Some(ToSave::Timestamps) {
success = sql::execute(
self.context,
@@ -466,7 +439,7 @@ impl<'a> Peerstate<'a> {
}
if self.to_save == Some(ToSave::All) || create {
reset_gossiped_timestamp(self.context, 0);
dc_reset_gossiped_timestamp(self.context, 0);
}
success
@@ -525,40 +498,6 @@ mod tests {
assert_eq!(peerstate, peerstate_new);
}
#[test]
fn test_peerstate_with_empty_gossip_key_save_to_db() {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64("xsBNBFztUVkBCADYaQl/UOUpRPd32nLRzx8eU0eI+jQEnG+g5anjYA+3oct1rROGl5SygjMULDKdaUy27O3o9Srsti0YjA7uxZnavIqhSopJhFidqY1M1wA9JZa/duucZdNwUGbjGIRsS/4Cjr5+3svscK24hVYub1dvDWXpwUTnj3K6xOEnJdoM+MhCqtSD5+zcJhFc9vyZm9ZTGWUxAhKh0iJTcCD8V6CQ3XZ2z9GruwzZT/FTFovWrz7m3TUI2OdSSHh0eZLRGEoxMCT/vzflAFGAr8ijCaRsEIfqP6FW8uQWnFTqkjxEUCZG6XkeFHB84aj5jqYG/1KCLjL5vEKwfl1tz/WnPhY7ABEBAAHNEDxoZWxsb0BtYWlsLmNvbT7CwIkEEAEIADMCGQEFAlztUVoCGwMECwkIBwYVCAkKCwIDFgIBFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7ijAwf+PTsuawUax9cNPn1bN90H+g9qyHZJMEwKXtUnNaXJxPW3iB7ThhpCiCzsZwP7+l7ArS8tmLeNDw2bENtcf1XCv4wovP2fdXOP3QOUUFX/GdakcTwv7DzC7CO0grB1HtaPhGw/6UX2o2cx2i9xiUf4Givq2MfCbgAW5zloH6WXGPb6yLQYJXxqDIphr4+uZDb+bMAyWHN/DUkAjHrV8nnVki7PMHqzzZpwglalxMX8RGeiGZE39ALJKL/Og87DMFah87/yoxQWGoS7Wqv0XDcCPKoTCPrpk8pOe2KEsq/lz215nefHd4aRpfUX5YCYa8HPvvfPQbGF73uvyQw5w7qjis7ATQRc7VFZAQgAt8ONdnX6KEEQ5Jw6ilJ+LBtY44SP5t0I3eK+goKepgIiKhjGDa+Mntyi4jdhH+HO6kvK5SHMh2sPp4rRO/WKHJwWFySyM1OdyiywhyH0J9R5rBY4vPHsJjf6vSKJdWLWT+ho1fNet2IIC+jVCYli91MAMbRvk6EKVj1nCc+67giOahXEkHt6xxkeCGlOvbw8hxGj1A8+AC1BLms/OR3oc4JMi9O3kq6uG0z9tlUEerac9HVwcjoO1XLe+hJhoT5H+TbnGjPuhuURP3pFiIKHpbRYgUfdSAY0dTObO7t4I5y/drPOrCTnWrBUg2wXAECUhpRKow9/ai2YemLv9KqhhwARAQABwsB2BBgBCAAgBQJc7VFaAhsMFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7jmyggAhs4QzCzIbT2OsAReBxkxtm0AI+g1HZ1KFKof5NDHfgv9C/Qu1I8mKEjlZzA4qFyPmLqntgwJ0RuFy6gLbljZBNCFO7vB478AhYtnWjuKZmA40HUPwcB1hEJ31c42akzfUbioY1TLLepngdsJg7Cm8O+rhI9+1WRA66haJDgFs793SVUDyJh8f9NX50l5zR87/bsV30CFSw0q4OSSy9VI/z+2g5khn1LnuuOrCfFnYIPYtJED1BfkXkosxGlgbzy79VvGmI9d23x4atDK7oBPCzIj+lP8sytJ0u3HOguXi9OgDitKy+Pt1r8gH8frdktMJr5Ts6DW+tIn2vR23KR8aA==", KeyType::Public).unwrap();
let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: Some(addr.into()),
last_seen: 10,
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,
verified_key: VerifiedKey::None,
verified_key_fingerprint: None,
to_save: Some(ToSave::All),
degrade_event: None,
};
assert!(peerstate.save_to_db(&ctx.ctx.sql, true), "failed to save");
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr.into())
.expect("failed to load peerstate from db");
// clear to_save, as that is not persissted
peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new);
}
// TODO: don't copy this from stress.rs
#[allow(dead_code)]
struct TestContext {

View File

@@ -2,7 +2,6 @@ use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::CStr;
use std::io::Cursor;
use std::ptr;
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
@@ -30,19 +29,19 @@ pub unsafe fn dc_split_armored_data(
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
let mut p2: *mut libc::c_char;
let mut headerline: *mut libc::c_char = ptr::null_mut();
let mut base64: *mut libc::c_char = ptr::null_mut();
let mut headerline: *mut libc::c_char = 0 as *mut libc::c_char;
let mut base64: *mut libc::c_char = 0 as *mut libc::c_char;
if !ret_headerline.is_null() {
*ret_headerline = ptr::null()
*ret_headerline = 0 as *const libc::c_char
}
if !ret_setupcodebegin.is_null() {
*ret_setupcodebegin = ptr::null_mut();
*ret_setupcodebegin = 0 as *const libc::c_char
}
if !ret_preferencrypt.is_null() {
*ret_preferencrypt = ptr::null();
*ret_preferencrypt = 0 as *const libc::c_char
}
if !ret_base64.is_null() {
*ret_base64 = ptr::null();
*ret_base64 = 0 as *const libc::c_char
}
if !(buf.is_null() || ret_headerline.is_null()) {
dc_remove_cr_chars(buf);
@@ -144,7 +143,7 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
.key_type(PgpKeyType::Rsa(2048))
.can_create_certificates(true)
.can_sign(true)
.primary_user_id(user_id)
.primary_user_id(user_id.into())
.passphrase(None)
.preferred_symmetric_algorithms(smallvec![
SymmetricKeyAlgorithm::AES256,

469
src/qr.rs
View File

@@ -1,469 +0,0 @@
use lazy_static::lazy_static;
use percent_encoding::percent_decode_str;
use crate::chat;
use crate::constants::Blocked;
use crate::contact::*;
use crate::context::Context;
use crate::error::Error;
use crate::key::dc_format_fingerprint;
use crate::key::*;
use crate::lot::{Lot, LotState};
use crate::param::*;
use crate::peerstate::*;
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
const MAILTO_SCHEME: &str = "mailto:";
const MATMSG_SCHEME: &str = "MATMSG:";
const VCARD_SCHEME: &str = "BEGIN:VCARD";
const SMTP_SCHEME: &str = "SMTP:";
const HTTP_SCHEME: &str = "http://";
const HTTPS_SCHEME: &str = "https://";
// Make it easy to convert errors into the final `Lot`.
impl Into<Lot> for Error {
fn into(self) -> Lot {
let mut l = Lot::new();
l.state = LotState::QrError;
l.text1 = Some(self.to_string());
l
}
}
/// Check a scanned QR code.
/// The function should be called after a QR code is scanned.
/// The function takes the raw text scanned and checks what can be done with it.
pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
let qr = qr.as_ref();
info!(context, 0, "Scanned QR code: {}", qr);
if qr.starts_with(OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr)
} else if qr.starts_with(MAILTO_SCHEME) {
decode_mailto(context, qr)
} else if qr.starts_with(SMTP_SCHEME) {
decode_smtp(context, qr)
} else if qr.starts_with(MATMSG_SCHEME) {
decode_matmsg(context, qr)
} else if qr.starts_with(VCARD_SCHEME) {
decode_vcard(context, qr)
} else if qr.starts_with(HTTP_SCHEME) || qr.starts_with(HTTPS_SCHEME) {
Lot::from_url(qr)
} else {
Lot::from_text(qr)
}
}
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let payload = &qr[OPENPGP4FPR_SCHEME.len()..];
let (fingerprint, fragment) = match payload.find('#').map(|offset| {
let (fp, rest) = payload.split_at(offset);
// need to remove the # from the fragment
(fp, &rest[1..])
}) {
Some(pair) => pair,
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) {
Ok(addr) => addr,
Err(err) => return err.into(),
}
} else {
return format_err!("Missing address").into();
};
// what is up with that param name?
let name = if let Some(encoded_name) = param.get(Param::SetLongitude) {
match percent_decode_str(encoded_name).decode_utf8() {
Ok(name) => name.to_string(),
Err(err) => return format_err!("Invalid name: {}", err).into(),
}
} else {
"".to_string()
};
let invitenumber = param.get(Param::ProfileImage).map(|s| s.to_string());
let auth = param.get(Param::Auth).map(|s| s.to_string());
let grpid = param.get(Param::GroupId).map(|s| s.to_string());
let grpname = if grpid.is_some() {
if let Some(encoded_name) = param.get(Param::GroupName) {
match percent_decode_str(encoded_name).decode_utf8() {
Ok(name) => Some(name.to_string()),
Err(err) => return format_err!("Invalid group name: {}", err).into(),
}
} else {
None
}
} else {
None
};
let fingerprint = dc_normalize_fingerprint(fingerprint);
// ensure valid fingerprint
if fingerprint.len() != 40 {
return format_err!("Bad fingerprint length in QR code").into();
}
println!(
"{:?} {:?} {:?} {:?} {:?} {:?} {:?}",
addr, name, invitenumber, auth, grpid, grpname, fingerprint
);
let mut lot = Lot::new();
// retrieve known state for this fingerprint
let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint);
if invitenumber.is_none() || auth.is_none() {
if let Some(peerstate) = peerstate {
lot.state = LotState::QrFprOk;
let addr = peerstate
.addr
.as_ref()
.map(|s| s.as_str())
.unwrap_or_else(|| "");
lot.id = Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan)
.map(|(id, _)| id)
.unwrap_or_default();
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
.unwrap_or_default();
chat::add_device_msg(
context,
id,
format!("{} verified.", peerstate.addr.unwrap_or_default()),
);
} else {
lot.state = LotState::QrFprWithoutAddr;
lot.text1 = Some(dc_format_fingerprint(&fingerprint));
}
} else {
if grpid.is_some() && grpname.is_some() {
lot.state = LotState::QrAskVerifyGroup;
lot.text1 = grpname;
lot.text2 = grpid
} else {
lot.state = LotState::QrAskVerifyContact;
}
lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan)
.map(|(id, _)| id)
.unwrap_or_default();
lot.fingerprint = Some(fingerprint);
lot.invitenumber = invitenumber;
lot.auth = auth;
}
lot
}
/// Extract address for the mailto scheme.
///
/// Scheme: `mailto:addr...?subject=...&body=..`
fn decode_mailto(context: &Context, qr: &str) -> Lot {
let payload = &qr[MAILTO_SCHEME.len()..];
let addr = if let Some(query_index) = payload.find('?') {
&payload[..query_index]
} else {
return format_err!("Invalid mailto found").into();
};
let addr = match normalize_address(addr) {
Ok(addr) => addr,
Err(err) => return err.into(),
};
let name = "".to_string();
Lot::from_address(context, name, addr)
}
/// Extract address for the smtp scheme.
///
/// Scheme: `SMTP:addr...:subject...:body...`
fn decode_smtp(context: &Context, qr: &str) -> Lot {
let payload = &qr[SMTP_SCHEME.len()..];
let addr = if let Some(query_index) = payload.find(':') {
&payload[..query_index]
} else {
return format_err!("Invalid SMTP found").into();
};
let addr = match normalize_address(addr) {
Ok(addr) => addr,
Err(err) => return err.into(),
};
let name = "".to_string();
Lot::from_address(context, name, addr)
}
/// Extract address for the matmsg scheme.
///
/// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;`
///
/// There may or may not be linebreaks after the fields.
fn decode_matmsg(context: &Context, qr: &str) -> Lot {
// Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field.
// we ignore this case.
let addr = if let Some(to_index) = qr.find("TO:") {
let addr = qr[to_index + 3..].trim();
if let Some(semi_index) = addr.find(';') {
addr[..semi_index].trim()
} else {
addr
}
} else {
return format_err!("Invalid MATMSG found").into();
};
let addr = match normalize_address(addr) {
Ok(addr) => addr,
Err(err) => return err.into(),
};
let name = "".to_string();
Lot::from_address(context, name, addr)
}
lazy_static! {
static ref VCARD_NAME_RE: regex::Regex =
regex::Regex::new(r"(?m)^N:([^;]*);([^;\n]*)").unwrap();
static ref VCARD_EMAIL_RE: regex::Regex =
regex::Regex::new(r"(?m)^EMAIL([^:\n]*):([^;\n]*)").unwrap();
}
/// Extract address for the matmsg scheme.
///
/// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;<type>:addr...;
fn decode_vcard(context: &Context, qr: &str) -> Lot {
let name = VCARD_NAME_RE
.captures(qr)
.map(|caps| {
let last_name = &caps[1];
let first_name = &caps[2];
format!("{} {}", first_name.trim(), last_name.trim())
})
.unwrap_or_default();
let addr = if let Some(caps) = VCARD_EMAIL_RE.captures(qr) {
match normalize_address(caps[2].trim()) {
Ok(addr) => addr,
Err(err) => return err.into(),
}
} else {
return format_err!("Bad e-mail address").into();
};
Lot::from_address(context, name, addr)
}
impl Lot {
pub fn from_text(text: impl AsRef<str>) -> Self {
let mut l = Lot::new();
l.state = LotState::QrText;
l.text1 = Some(text.as_ref().to_string());
l
}
pub fn from_url(url: impl AsRef<str>) -> Self {
let mut l = Lot::new();
l.state = LotState::QrUrl;
l.text1 = Some(url.as_ref().to_string());
l
}
pub fn from_address(context: &Context, name: String, addr: String) -> Self {
let mut l = Lot::new();
l.state = LotState::QrAddr;
l.id = match Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan) {
Ok((id, _)) => id,
Err(err) => return err.into(),
};
l
}
}
/// URL decodes a given address, does basic email validation on the result.
fn normalize_address(addr: &str) -> Result<String, Error> {
// urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases
let new_addr = percent_decode_str(addr).decode_utf8()?;
let new_addr = addr_normalize(&new_addr);
ensure!(may_be_valid_addr(&new_addr), "Bad e-mail address");
Ok(new_addr.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::dummy_context;
#[test]
fn test_decode_http() {
let ctx = dummy_context();
let res = check_qr(&ctx.ctx, "http://www.hello.com");
assert_eq!(res.get_state(), LotState::QrUrl);
assert_eq!(res.get_id(), 0);
assert_eq!(res.get_text1().unwrap(), "http://www.hello.com");
assert!(res.get_text2().is_none());
}
#[test]
fn test_decode_https() {
let ctx = dummy_context();
let res = check_qr(&ctx.ctx, "https://www.hello.com");
assert_eq!(res.get_state(), LotState::QrUrl);
assert_eq!(res.get_id(), 0);
assert_eq!(res.get_text1().unwrap(), "https://www.hello.com");
assert!(res.get_text2().is_none());
}
#[test]
fn test_decode_text() {
let ctx = dummy_context();
let res = check_qr(&ctx.ctx, "I am so cool");
assert_eq!(res.get_state(), LotState::QrText);
assert_eq!(res.get_id(), 0);
assert_eq!(res.get_text1().unwrap(), "I am so cool");
assert!(res.get_text2().is_none());
}
#[test]
fn test_decode_vcard() {
let ctx = dummy_context();
let res = check_qr(
&ctx.ctx,
"BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD"
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "stress@test.local");
assert_eq!(contact.get_name(), "First Last");
}
#[test]
fn test_decode_matmsg() {
let ctx = dummy_context();
let res = check_qr(
&ctx.ctx,
"MATMSG:TO:\n\nstress@test.local ; \n\nSUB:\n\nSubject here\n\nBODY:\n\nhelloworld\n;;",
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "stress@test.local");
}
#[test]
fn test_decode_mailto() {
let ctx = dummy_context();
let res = check_qr(
&ctx.ctx,
"mailto:stress@test.local?subject=hello&body=world",
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "stress@test.local");
}
#[test]
fn test_decode_smtp() {
let ctx = dummy_context();
let res = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld");
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "stress@test.local");
}
#[test]
fn test_decode_openpgp_group() {
let ctx = dummy_context();
let res = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=testtesttest&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAskVerifyGroup);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "cli@deltachat.de");
}
#[test]
fn test_decode_openpgp_secure_join() {
let ctx = dummy_context();
let res = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
);
println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAskVerifyContact);
assert_ne!(res.get_id(), 0);
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
assert_eq!(contact.get_addr(), "cli@deltachat.de");
}
}

View File

@@ -4,6 +4,7 @@ use std::sync::{Arc, RwLock};
use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS};
use thread_local_object::ThreadLocal;
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::{Error, Result};
@@ -286,7 +287,7 @@ impl Sql {
fn table_exists(conn: &Connection, name: impl AsRef<str>) -> Result<bool> {
let mut exists = false;
conn.pragma(None, "table_info", &name.as_ref().to_string(), |_row| {
conn.pragma(None, "table_info", &format!("{}", name.as_ref()), |_row| {
// will only be executed if the info was found
exists = true;
Ok(())
@@ -688,6 +689,10 @@ fn open(
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
params![],
)?;
assert_eq!(DC_MOVE_STATE_UNDEFINED as libc::c_int, 0);
assert_eq!(DC_MOVE_STATE_PENDING as libc::c_int, 1);
assert_eq!(DC_MOVE_STATE_STAY as libc::c_int, 2);
assert_eq!(DC_MOVE_STATE_MOVING as libc::c_int, 3);
dbversion = 48;
sql.set_config_int(context, "dbversion", 48)?;
@@ -830,7 +835,7 @@ where
&err,
querystr.as_ref()
);
Err(err)
Err(err.into())
}
}
}
@@ -1003,10 +1008,10 @@ pub fn housekeeping(context: &Context) {
let name_f = entry.file_name();
let name_s = name_f.to_string_lossy();
if is_file_in_use(&files_in_use, None, &name_s)
|| is_file_in_use(&files_in_use, Some(".increation"), &name_s)
|| is_file_in_use(&files_in_use, Some(".waveform"), &name_s)
|| is_file_in_use(&files_in_use, Some("-preview.jpg"), &name_s)
if is_file_in_use(&mut files_in_use, None, &name_s)
|| is_file_in_use(&mut files_in_use, Some(".increation"), &name_s)
|| is_file_in_use(&mut files_in_use, Some(".waveform"), &name_s)
|| is_file_in_use(&mut files_in_use, Some("-preview.jpg"), &name_s)
{
continue;
}
@@ -1042,8 +1047,8 @@ pub fn housekeeping(context: &Context) {
unreferenced_count,
entry.file_name()
);
let path = entry.path();
dc_delete_file(context, path);
let path = entry.path().to_c_string().unwrap();
dc_delete_file(context, path.as_ptr());
}
}
Err(err) => {

View File

@@ -323,7 +323,7 @@ mod tests {
StockMessage::MsgAddMember,
"alice@example.com",
"",
DC_CONTACT_ID_SELF
DC_CONTACT_ID_SELF as u32
),
"Member alice@example.com added by me."
)
@@ -338,7 +338,7 @@ mod tests {
StockMessage::MsgAddMember,
"alice@example.com",
"",
DC_CONTACT_ID_SELF
DC_CONTACT_ID_SELF as u32
),
"Member Alice (alice@example.com) added by me."
);
@@ -373,7 +373,7 @@ mod tests {
StockMessage::MsgGrpName,
"Some chat",
"Other chat",
DC_CONTACT_ID_SELF
DC_CONTACT_ID_SELF as u32
),
"Group name changed from \"Some chat\" to \"Other chat\" by me."
)

View File

@@ -2,15 +2,9 @@
//!
//! This module is only compiled for test runs.
use std::ffi::CStr;
use libc::uintptr_t;
use tempfile::{tempdir, TempDir};
use crate::config::Config;
use crate::constants::{Event, KeyType};
use crate::context::{dc_context_new, dc_open, Context};
use crate::key;
use crate::types::dc_callback_t;
/// A Context and temporary directory.
@@ -50,127 +44,3 @@ pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
pub fn dummy_context() -> TestContext {
test_context(None)
}
pub unsafe extern "C" fn logging_cb(
_ctx: &Context,
evt: Event,
_d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap();
match evt {
Event::INFO => println!("I: {}", to_str(d2)),
Event::WARNING => println!("W: {}", to_str(d2)),
Event::ERROR => println!("E: {}", to_str(d2)),
_ => (),
}
0
}
/// Creates Alice with a pre-generated keypair.
///
/// Returns the address of the keypair created (alice@example.org).
pub fn configure_alice_keypair(ctx: &Context) -> String {
let addr = String::from("alice@example.org");
ctx.set_config(Config::ConfiguredAddr, Some(&addr)).unwrap();
// The keypair was created using:
// let (public, private) = crate::pgp::dc_pgp_create_keypair("alice@example.com")
// .unwrap();
// println!("{}", public.to_base64(64));
// println!("{}", private.to_base64(64));
let public = key::Key::from_base64(
concat!(
"xsBNBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1l",
"FrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSX",
"AMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4",
"Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9",
"iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchw",
"oFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAHNEzxhbGljZUBleGFtcGxl",
"LmNvbT7CwIkEEAEIADMCGQEFAl086fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iai",
"x4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9",
"OHUl3MrXtZ7QmHyOAFvbXE/6n5Eeh+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkK",
"A8e4cJqwDOHsyAnvQXZ7WNje9+BMzcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea",
"6zjGF0/qljTdoxTtsYpv5wXYuhwbYklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6",
"GkquJN814Y+xny4xhZzGOfue6SeP12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUK",
"u5wO9FFbgDySOSlEjByGejSGuBmho0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxD",
"Fc7ATQRdPOnsAQgA5oLxXRLnyugzOmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG",
"9JzDeQql+sYXgUSxOoIayItuXtnFn7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av",
"62n18Venlm0yNKpROPcZ6M/sc4m6uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/R",
"noW+/fhmwIg08dQ5m8hQe3GEOZEeLrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q",
"4zW8vk2ztB8ngwbnqYy8zrN1DCICN1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAm",
"jxLZfVDcoObFH3Cv2GB7BEYxv86KC2Y6T74Q/wARAQABwsB2BBgBCAAgBQJdPOn4",
"AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/dXshJnoW",
"qEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJywJoupwX",
"FNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJuiCQvR9m",
"MjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6RDXIeYJf",
"qrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMlammDliPw",
"sK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKObzPqgJCGw",
"jTglkixw+aSTXw=="
),
KeyType::Public,
)
.unwrap();
let private = key::Key::from_base64(
concat!(
"xcLYBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1l",
"FrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSX",
"AMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4",
"Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9",
"iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchw",
"oFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAEACAChqzVOuErmVRqvcYtq",
"m1xt1H+ZjX20z5Sn1fhTLYAcq236AWMqJvwxCXoKlc8bt2UfB+Ls9cQb1YcVq353",
"r0QiExiDeK3YlCxqd/peXJwFYTNKFC3QcnUhtpG9oS/jWjN+BRotGbjtu6Vj3M68",
"JJAq+mHJ0/9OyrqrREvGfo7uLZt7iMGemDlrDakvrbIyZrPLgay+nZ3dEFKeOQ6F",
"FrU05jyUVdoHBy0Tqx/6VpFUX9+IHcMHL2lTJB0nynBj+XZ/G4aX3WYoo3YlixHb",
"Iu35fGFA0TChoGaGPzqcI/kg2Z+b/BryG9NM3LA2cO8iGrGXAE1nPFp91jmCrQ3V",
"WushBADERP+uojjjfdO5J+RkmcFe9mFYDdtkhN+kV+LdePjiNNtcXMBhasstio0S",
"ut0GKnE7DFRhX7mkN9w2apJ2ooeFeVVWot18eSdp6Rzh6/1Z7TmhYFJ3oUxxLbnQ",
"sWIXIec1SzqWBFJUCn3IP0mCnJktFg/uGW6yLs01r5ds52uSBQQA2LSWiTwk9tEm",
"dr9mz3tHnmrkyGiyKhKGM1Z7Rch63D5yQc1s4kUMBlyuLL2QtM/e4dtaz2JAkO8k",
"QrYCnNgJ+2roTAK3kDZgYtymjdvK3HpQNtjVo7dds5RJVb6U618phZwU5WNFAEJW",
"yyImmycGfjLv+18cW/3mq0QVZejkM78D/2kHaIeJAowtBOFY2zDrKyDRoBHaUSgj",
"5BjGoviRC5rYihWDEyYDQ6mBJQstAD0Ty3MYzyUxl6ruB/BMWnMDFq5+TqtdBzu3",
"jCtZ8OEyH8A5Kdo68Wzo/PGxzMtusOdNj9+3PBmSq4yibJxbLSrn59aVUYpGLjeG",
"Kyvm9OTKkrOGN27NEzxhbGljZUBleGFtcGxlLmNvbT7CwIkEEAEIADMCGQEFAl08",
"6fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQ",
"k6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9OHUl3MrXtZ7QmHyOAFvbXE/6n5Ee",
"h+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkKA8e4cJqwDOHsyAnvQXZ7WNje9+BM",
"zcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea6zjGF0/qljTdoxTtsYpv5wXYuhwb",
"YklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6GkquJN814Y+xny4xhZzGOfue6SeP",
"12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUKu5wO9FFbgDySOSlEjByGejSGuBmh",
"o0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxDFcfC2ARdPOnsAQgA5oLxXRLnyugz",
"OmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG9JzDeQql+sYXgUSxOoIayItuXtnF",
"n7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av62n18Venlm0yNKpROPcZ6M/sc4m6",
"uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/RnoW+/fhmwIg08dQ5m8hQe3GEOZEe",
"LrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q4zW8vk2ztB8ngwbnqYy8zrN1DCIC",
"N1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAmjxLZfVDcoObFH3Cv2GB7BEYxv86K",
"C2Y6T74Q/wARAQABAAgAhSvFEYZoj1sSrXrHDjZOrryViGjCCH9t3pmkxLDrGIdd",
"KsFyN8ORUo6KUZS745yx3yFnI9EZ1IZvm9aF+jxk2lGJFtgLvfoxFOvGckwCSy8T",
"/MCiJZkz01hWo5s2VCLJheWL/GqTKjS5wXDcm+y8Wtilh+UawycdlDsSNr/D4MZL",
"j3Chq9K03l5UIR8DcC7SavNi55R2oGOfboXsdvwOlrNZdCkZOlXDI4ZKFwbDHCtp",
"Do5FS30hnJi2TecUPZWB1CaGFWnevINd4ikugVjcAoZj/QAIvfrOCgqisF/Ylg9u",
"RMUPBapmcJUueILwd0iQqvGG0aCqtchvSmlg15/lQQQA9G1NNjNAH+NQrXvDJFJe",
"/V1U3F3pz7jCjQa69c0dxSBUeNX1pG8XXD6tSkkd4Ni1mzZGcZXOmVUM6cA9I7RH",
"95RqV+QIfnXVneCRrlCjV8m6OBlkivkESXc3nW5wtCIfw7oKg9w1xuVNUaAlbCt9",
"QVLaxXJiY7ad0f5U9XJ1+w8EAPFs+M/+GZK1wOZYBL1vo7x0gL9ZggmjC4B+viBJ",
"8Q60mqTrphYFsbXHuwKV0g9aIoZMucKyEE0QLR7imttiLEz1nD8bfEScbGy9ZG//",
"wRfyJmCVAjA0pQ6LtB93d70PSVzzJrMHgbLKrDuSd6RChl7n9BIEdVyk7LEph0Yg",
"9UsRBADm6DvpKL+P3lQ0eLTfAgcQTOqLZDYmI3PvqqSkHb1kHChqOXXs8hGOSSwK",
"Gjcd4CZeNOGWR42rZyRhVgtkt6iYviIaVAWUfme6K+sLQBCeyMlmEGtykAA+LmPB",
"f4zdyUNADfoxgZF3EKHf6I3nlVn5cdT+o/9vjdY2XAOwcls1RzaFwsB2BBgBCAAg",
"BQJdPOn4AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/",
"dXshJnoWqEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJ",
"ywJoupwXFNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJ",
"uiCQvR9mMjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6",
"RDXIeYJfqrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMl",
"ammDliPwsK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKOb",
"zPqgJCGwjTglkixw+aSTXw=="
),
KeyType::Private,
)
.unwrap();
let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, 1, &ctx.sql);
assert_eq!(saved, true, "Failed to save Alice's key");
addr
}

View File

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

View File

@@ -16,6 +16,8 @@ pub use rusqlite::ffi::*;
pub type dc_callback_t =
unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t;
pub type dc_move_state_t = u32;
pub type dc_receive_imf_t = unsafe fn(
_: &Context,
_: *const libc::c_char,
@@ -34,9 +36,14 @@ pub type dc_precheck_imf_t =
pub type dc_set_config_t = fn(_: &Context, _: &str, _: Option<&str>) -> ();
pub type dc_get_config_t = fn(_: &Context, _: &str) -> Option<String>;
pub type sqlite_int64 = i64;
pub type sqlite3_int64 = sqlite_int64;
pub type int32_t = i32;
pub type int64_t = i64;
pub type uintptr_t = libc::uintptr_t;
pub type size_t = libc::size_t;
pub type ssize_t = libc::ssize_t;
pub type uint32_t = libc::c_uint;
pub type uint8_t = libc::c_uchar;
pub type uint16_t = libc::c_ushort;

View File

@@ -54,6 +54,27 @@ pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char
}
}
pub(crate) unsafe fn strncasecmp(
s1: *const libc::c_char,
s2: *const libc::c_char,
n: libc::size_t,
) -> libc::c_int {
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()
.to_lowercase();
let s2 = std::ffi::CStr::from_ptr(s2)
.to_string_lossy()
.to_lowercase();
let m1 = std::cmp::min(n, s1.len());
let m2 = std::cmp::min(n, s2.len());
if s1[..m1] == s2[..m2] {
0
} else {
1
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -2,16 +2,24 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use mmime::mailimf_types::*;
use tempfile::{tempdir, TempDir};
use deltachat::chat::{self, Chat};
use deltachat::config;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_array::*;
use deltachat::dc_chat::*;
use deltachat::dc_configure::*;
use deltachat::dc_imex::*;
use deltachat::dc_location::*;
use deltachat::dc_lot::*;
use deltachat::dc_mimeparser::*;
use deltachat::dc_qr::*;
use deltachat::dc_saxparser::*;
use deltachat::dc_securejoin::*;
use deltachat::dc_tools::*;
use deltachat::key::*;
use deltachat::keyring::*;
@@ -32,16 +40,56 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
as *const u8 as *const libc::c_char;
unsafe fn stress_functions(context: &Context) {
let mut saxparser: dc_saxparser_t = dc_saxparser_t {
starttag_cb: None,
endtag_cb: None,
text_cb: None,
userdata: 0 as *mut libc::c_void,
};
dc_saxparser_init(&mut saxparser, 0 as *mut libc::c_void);
dc_saxparser_parse(
&mut saxparser,
b"<tag attr=val=\x00" as *const u8 as *const libc::c_char,
);
dc_saxparser_parse(
&mut saxparser,
b"<tag attr=\"val\"=\x00" as *const u8 as *const libc::c_char,
);
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")
if 0 != dc_file_exist(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
) || 0
!= dc_file_exist(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
)
|| 0 != dc_file_exist(
context,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
)
|| 0 != dc_file_exist(
context,
b"$BLOBDIR/foobar-folder\x00" as *const u8 as *const libc::c_char,
)
{
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_delete_file(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
);
dc_delete_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
);
dc_delete_file(
context,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
dc_delete_file(
context,
b"$BLOBDIR/foobar-folder\x00" as *const u8 as *const libc::c_char,
);
}
dc_write_file(
context,
@@ -49,10 +97,25 @@ unsafe fn stress_functions(context: &Context) {
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_ne!(
0,
dc_file_exist(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
)
);
assert_eq!(
dc_get_filebytes(context, "$BLOBDIR/foobar",),
0,
dc_file_exist(
context,
b"$BLOBDIR/foobarx\x00" as *const u8 as *const libc::c_char,
)
);
assert_eq!(
dc_get_filebytes(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
),
7i32 as libc::c_ulonglong
);
@@ -61,15 +124,34 @@ unsafe fn stress_functions(context: &Context) {
context.get_blobdir(),
b"foobar\x00" as *const u8 as *const libc::c_char,
);
assert!(dc_is_blobdir_path(context, 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)));
assert!(dc_is_blobdir_path(context, abs_path));
assert!(dc_is_blobdir_path(
context,
b"$BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char,
));
assert!(!dc_is_blobdir_path(
context,
b"/BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char,
));
assert_ne!(0, dc_file_exist(context, 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_ne!(
0,
dc_copy_file(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
)
);
assert_eq!(
dc_get_filebytes(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
),
7
);
let mut buf: *mut libc::c_void = ptr::null_mut();
let mut buf: *mut libc::c_void = 0 as *mut libc::c_void;
let mut buf_bytes: size_t = 0;
assert_eq!(
@@ -88,11 +170,41 @@ unsafe fn stress_functions(context: &Context) {
);
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"));
assert_ne!(
0,
dc_delete_file(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
)
);
assert_ne!(
0,
dc_delete_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
)
);
assert_ne!(
0,
dc_create_folder(
context,
b"$BLOBDIR/foobar-folder\x00" as *const u8 as *const libc::c_char,
)
);
assert_ne!(
0,
dc_file_exist(
context,
b"$BLOBDIR/foobar-folder\x00" as *const u8 as *const libc::c_char,
)
);
assert_ne!(
0,
dc_delete_file(
context,
b"$BLOBDIR/foobar-folder\x00" as *const u8 as *const libc::c_char,
)
);
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
@@ -125,7 +237,7 @@ unsafe fn stress_functions(context: &Context) {
),
0
);
assert!(dc_delete_file(context, as_path(fn0)));
assert_ne!(0, dc_delete_file(context, fn0));
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
}
@@ -162,10 +274,10 @@ unsafe fn stress_functions(context: &Context) {
assert!(res.contains(" configured_server_flags "));
let mut buf_0: *mut libc::c_char;
let mut headerline: *const libc::c_char = ptr::null();
let mut setupcodebegin: *const libc::c_char = ptr::null();
let mut preferencrypt: *const libc::c_char = ptr::null();
let mut base64: *const libc::c_char = ptr::null();
let mut headerline: *const libc::c_char = 0 as *const libc::c_char;
let mut setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
let mut preferencrypt: *const libc::c_char = 0 as *const libc::c_char;
let mut base64: *const libc::c_char = 0 as *const libc::c_char;
buf_0 = strdup(
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\ndata\n-----END PGP MESSAGE-----\x00" as *const u8
as *const libc::c_char,
@@ -174,7 +286,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
assert!(ok);
@@ -199,7 +311,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
@@ -226,7 +338,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
@@ -251,7 +363,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
@@ -264,7 +376,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
assert!(ok);
@@ -297,7 +409,7 @@ unsafe fn stress_functions(context: &Context) {
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut preferencrypt,
&mut base64,
);
@@ -353,16 +465,16 @@ unsafe fn stress_functions(context: &Context) {
);
free(norm as *mut libc::c_void);
let mut buf_1: *mut libc::c_char;
let mut headerline_0: *const libc::c_char = ptr::null();
let mut setupcodebegin_0: *const libc::c_char = ptr::null();
let mut preferencrypt_0: *const libc::c_char = ptr::null();
let mut headerline_0: *const libc::c_char = 0 as *const libc::c_char;
let mut setupcodebegin_0: *const libc::c_char = 0 as *const libc::c_char;
let mut preferencrypt_0: *const libc::c_char = 0 as *const libc::c_char;
buf_1 = strdup(S_EM_SETUPFILE);
assert!(dc_split_armored_data(
buf_1,
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
ptr::null_mut(),
0 as *mut *const libc::c_char,
));
assert!(!headerline_0.is_null());
assert_eq!(
@@ -388,7 +500,7 @@ unsafe fn stress_functions(context: &Context) {
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
ptr::null_mut(),
0 as *mut *const libc::c_char,
));
assert!(!headerline_0.is_null());
assert_eq!(
@@ -408,55 +520,63 @@ unsafe fn stress_functions(context: &Context) {
0
);
free(buf_1 as *mut libc::c_void);
if 0 != dc_is_configured(context) {
let setupcode = dc_create_setup_code(context);
let setupcode_c = CString::yolo(setupcode.clone());
let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
let setupfile_c = CString::yolo(setupfile);
let payload: *mut libc::c_char;
let mut headerline_2: *const libc::c_char = 0 as *const libc::c_char;
payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
assert!(payload.is_null());
assert!(!dc_split_armored_data(
payload,
&mut headerline_2,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
));
assert!(!headerline_2.is_null());
assert_eq!(
strcmp(
headerline_2,
b"-----BEGIN PGP PRIVATE KEY BLOCK-----\x00" as *const u8 as *const libc::c_char,
),
0
);
free(payload as *mut libc::c_void);
}
// Cant check, no configured context
// assert!(dc_is_configured(context) != 0, "Missing configured context");
if 0 != dc_is_configured(context) {
let qr: *mut libc::c_char = dc_get_securejoin_qr(context, 0i32 as uint32_t);
assert!(
!(strlen(qr) > 55
&& strncmp(
qr,
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
12,
) == 0i32
&& strncmp(
&mut *qr.offset(52isize),
b"#a=\x00" as *const u8 as *const libc::c_char,
3,
) == 0i32)
);
let mut res: *mut dc_lot_t = dc_check_qr(context, qr);
assert!(res.is_null());
assert!(!((*res).state == 200i32 || (*res).state == 220i32 || (*res).state == 230i32));
// let setupcode = dc_create_setup_code(context);
// let setupcode_c = CString::yolo(setupcode.clone());
// let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
// let setupfile_c = CString::yolo(setupfile);
// let mut headerline_2: *const libc::c_char = ptr::null();
// let payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
// assert!(payload.is_null());
// assert!(!dc_split_armored_data(
// payload,
// &mut headerline_2,
// ptr::null_mut(),
// ptr::null_mut(),
// ptr::null_mut(),
// ));
// assert!(!headerline_2.is_null());
// assert_eq!(
// strcmp(
// headerline_2,
// b"-----BEGIN PGP PRIVATE KEY BLOCK-----\x00" as *const u8 as *const libc::c_char,
// ),
// 0
// );
// free(payload as *mut libc::c_void);
// Cant check, no configured context
// assert!(dc_is_configured(context) != 0, "missing configured context");
// let qr = dc_get_securejoin_qr(context, 0);
// assert!(!qr.is_null(), "Invalid qr code generated");
// let qr_r = as_str(qr);
// assert!(qr_r.len() > 55);
// assert!(qr_r.starts_with("OPENPGP4FPR:"));
// let res = dc_check_qr(context, qr);
// let s = res.get_state();
// assert!(
// s == QrState::AskVerifyContact
// || s == QrState::FprMissmatch
// || s == QrState::FprWithoutAddr
// );
// free(qr.cast());
dc_lot_unref(res);
free(qr as *mut libc::c_void);
res =
dc_check_qr(context,
b"BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD\x00"
as *const u8 as *const libc::c_char);
assert!(res.is_null());
assert!(!((*res).state == 320i32));
assert!(!((*res).id != 0i32 as libc::c_uint));
dc_lot_unref(res);
};
}
#[test]
@@ -652,6 +772,82 @@ unsafe fn create_test_context() -> TestContext {
TestContext { ctx: ctx, dir: dir }
}
#[test]
fn test_dc_kml_parse() {
unsafe {
let context = create_test_context();
let xml: *const libc::c_char =
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</wHeN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></Placemark>\n</Document>\n</kml>\x00"
as *const u8 as *const libc::c_char;
let mut kml = dc_kml_parse(&context.ctx, xml, strlen(xml));
assert!(!kml.addr.is_null());
assert_eq!(as_str(kml.addr as *const libc::c_char), "user@example.org",);
let locations_ref = &kml.locations.as_ref().unwrap();
assert_eq!(locations_ref.len(), 2);
assert!(locations_ref[0].latitude > 53.6f64);
assert!(locations_ref[0].latitude < 53.8f64);
assert!(locations_ref[0].longitude > 9.3f64);
assert!(locations_ref[0].longitude < 9.5f64);
assert!(locations_ref[0].accuracy > 31.9f64);
assert!(locations_ref[0].accuracy < 32.1f64);
assert_eq!(locations_ref[0].timestamp, 1551906597);
assert!(locations_ref[1].latitude > 63.6f64);
assert!(locations_ref[1].latitude < 63.8f64);
assert!(locations_ref[1].longitude > 19.3f64);
assert!(locations_ref[1].longitude < 19.5f64);
assert!(locations_ref[1].accuracy > 2.4f64);
assert!(locations_ref[1].accuracy < 2.6f64);
assert_eq!(locations_ref[1].timestamp, 1544739072);
dc_kml_unref(&mut kml);
}
}
#[test]
fn test_dc_mimeparser_with_context() {
unsafe {
let context = create_test_context();
let mut mimeparser = dc_mimeparser_new(&context.ctx);
let raw: *const libc::c_char =
b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00"
as *const u8 as *const libc::c_char;
dc_mimeparser_parse(&mut mimeparser, raw, strlen(raw));
assert_eq!(
as_str(mimeparser.subject as *const libc::c_char),
"inner-subject",
);
let mut of: *mut mailimf_optional_field = dc_mimeparser_lookup_optional_field(
&mimeparser,
b"X-Special-A\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "special-a",);
of = dc_mimeparser_lookup_optional_field(
&mimeparser,
b"Foo\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "Bar",);
of = dc_mimeparser_lookup_optional_field(
&mimeparser,
b"Chat-Version\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "1.0",);
assert_eq!(mimeparser.parts.len(), 1);
dc_mimeparser_unref(&mut mimeparser);
}
}
#[test]
fn test_dc_get_oauth2_url() {
let ctx = unsafe { create_test_context() };
@@ -695,16 +891,19 @@ fn test_get_contacts() {
unsafe {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
assert_eq!(dc_array_get_cnt(contacts), 0);
dc_array_unref(contacts);
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);
assert_eq!(dc_array_get_cnt(contacts), 1);
dc_array_unref(contacts);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
assert_eq!(dc_array_get_cnt(contacts), 0);
dc_array_unref(contacts);
}
}
@@ -715,15 +914,17 @@ fn test_chat() {
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();
let chat_id = dc_create_chat_by_contact_id(&context.ctx, contact1);
assert!(chat_id > 9, "chat_id too small {}", chat_id);
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
let chat = dc_chat_new(&context.ctx);
assert!(dc_chat_load_from_db(chat, chat_id));
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
let chat2_id = dc_create_chat_by_contact_id(&context.ctx, contact1);
assert_eq!(chat2_id, chat_id);
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
let chat2 = dc_chat_new(&context.ctx);
assert!(dc_chat_load_from_db(chat2, chat2_id));
assert_eq!(chat2.name, chat.name);
assert_eq!(as_str((*chat2).name), as_str((*chat).name));
}
}