mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 13:32:11 +03:00
Compare commits
48 Commits
fix_upload
...
eventloggi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bc5d1b90e | ||
|
|
197d94ad9d | ||
|
|
7ce337c6d0 | ||
|
|
10148d2e43 | ||
|
|
69dc237ee3 | ||
|
|
df5464ea80 | ||
|
|
e4bf9956a5 | ||
|
|
d353d9d9d8 | ||
|
|
1ad45ed4d6 | ||
|
|
496e980a17 | ||
|
|
fa09e46ed9 | ||
|
|
d6de420b9a | ||
|
|
38eb708db8 | ||
|
|
7a59da5f8f | ||
|
|
f13a1d4a2f | ||
|
|
7b3a450918 | ||
|
|
169923b102 | ||
|
|
42688a0622 | ||
|
|
35f3c0edd1 | ||
|
|
e7a236264a | ||
|
|
aaa5b820d9 | ||
|
|
e7f0745010 | ||
|
|
c68e7ae14e | ||
|
|
618087e5a7 | ||
|
|
245abb8384 | ||
|
|
a3e1042001 | ||
|
|
7b7ce9348f | ||
|
|
7a4808ba0d | ||
|
|
8f240f7153 | ||
|
|
7d0b5d8abb | ||
|
|
ee317cb1b5 | ||
|
|
7b736fe635 | ||
|
|
c7db15352a | ||
|
|
0b37167be8 | ||
|
|
5cac4b5076 | ||
|
|
475a41beb3 | ||
|
|
ad4be80b4e | ||
|
|
8737c1d142 | ||
|
|
964fe466cc | ||
|
|
43936e7db7 | ||
|
|
0e80ce9c39 | ||
|
|
c652bae68a | ||
|
|
bc904a495d | ||
|
|
8d99444c6a | ||
|
|
9dab53e0af | ||
|
|
360089ac74 | ||
|
|
e892c5cf4d | ||
|
|
9ad4c9a6fe |
@@ -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
|
||||
@@ -179,16 +169,12 @@ workflows:
|
||||
- build_test_docs_wheel
|
||||
- upload_docs_wheels:
|
||||
requires:
|
||||
- build_test_docs_wheel
|
||||
- build_test_docs_wheel
|
||||
- cargo_fetch
|
||||
- rustfmt:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
|
||||
- clippy:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
|
||||
# Linux Desktop 64bit
|
||||
- test_x86_64-unknown-linux-gnu:
|
||||
requires:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,7 +18,3 @@ __pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
|
||||
python/liveconfig*
|
||||
|
||||
# ignore doxgen generated files
|
||||
deltachat-ffi/html
|
||||
deltachat-ffi/xml
|
||||
|
||||
911
Cargo.lock
generated
911
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@@ -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"
|
||||
@@ -27,7 +25,7 @@ imap = "1.0.1"
|
||||
mmime = "0.1.0"
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
percent-encoding = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = "0.4.6"
|
||||
@@ -37,19 +35,14 @@ failure_derive = "0.1.5"
|
||||
rustyline = "4.1.0"
|
||||
lazy_static = "1.3.0"
|
||||
regex = "1.1.6"
|
||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
||||
r2d2_sqlite = "0.12.0"
|
||||
rusqlite = { version = "0.19", features = ["bundled"] }
|
||||
addr = "0.2.0"
|
||||
r2d2_sqlite = "0.11.0"
|
||||
r2d2 = "0.8.5"
|
||||
strum = "0.15.0"
|
||||
strum_macros = "0.15.0"
|
||||
thread-local-object = "0.1.0"
|
||||
backtrace = "0.3.33"
|
||||
byteorder = "1.3.1"
|
||||
itertools = "0.8.0"
|
||||
image-meta = "0.1.0"
|
||||
quick-xml = "0.15.0"
|
||||
escaper = "0.1.0"
|
||||
bitflags = "1.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
@@ -58,10 +51,13 @@ pretty_env_logger = "0.3.0"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-ffi"
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
rusqlite = { git = "http://github.com/dignifiedquire/rusqlite", branch = "fix/text", features = ["bundled"] }
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "simple"
|
||||
path = "examples/simple.rs"
|
||||
|
||||
13
README.md
13
README.md
@@ -63,11 +63,6 @@ Single#10: yourfriends@email.org [yourfriends@email.org]
|
||||
Message sent.
|
||||
```
|
||||
|
||||
If `yourfriend@email.org` uses DeltaChat, but does not receive message just
|
||||
sent, it is advisable to check `Spam` folder. It is known that at least
|
||||
`gmx.com` treat such test messages as spam, unless told otherwise with web
|
||||
interface.
|
||||
|
||||
List messages when inside a chat:
|
||||
|
||||
```
|
||||
@@ -89,14 +84,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.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
[dependencies.std]
|
||||
features = ["panic-unwind"]
|
||||
|
||||
# if using `cargo test`
|
||||
[dependencies.test]
|
||||
stage = 1
|
||||
@@ -4,7 +4,6 @@ set -ex
|
||||
|
||||
export RUST_TEST_THREADS=1
|
||||
export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS='--deny warnings'
|
||||
export OPT="--target=$TARGET"
|
||||
export OPT_RELEASE="--release ${OPT}"
|
||||
export OPT_FFI_RELEASE="--manifest-path=deltachat-ffi/Cargo.toml --release"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -18,10 +18,10 @@ crate-type = ["cdylib", "staticlib"]
|
||||
deltachat = { path = "../", default-features = false }
|
||||
libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
|
||||
[features]
|
||||
default = ["vendored", "nightly", "ringbuf"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
nightly = ["deltachat/nightly"]
|
||||
ringbuf = ["deltachat/ringbuf"]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let target_path = out_path.join("../../..");
|
||||
let target_triple = env::var("TARGET").unwrap();
|
||||
|
||||
// macOS or iOS, inherited from rpgp
|
||||
let libs_priv = if target_triple.contains("apple") || target_triple.contains("darwin") {
|
||||
// needed for OsRng
|
||||
"-framework Security -framework Foundation"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let pkg_config = format!(
|
||||
include_str!("deltachat.pc.in"),
|
||||
name = "deltachat",
|
||||
description = env::var("CARGO_PKG_DESCRIPTION").unwrap(),
|
||||
url = env::var("CARGO_PKG_HOMEPAGE").unwrap_or("".to_string()),
|
||||
version = env::var("CARGO_PKG_VERSION").unwrap(),
|
||||
libs_priv = libs_priv,
|
||||
prefix = env::var("PREFIX").unwrap_or("/usr/local".to_string()),
|
||||
);
|
||||
|
||||
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
|
||||
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
|
||||
.unwrap()
|
||||
.write_all(&pkg_config.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
prefix={prefix}
|
||||
libdir=${{prefix}}/lib
|
||||
includedir=${{prefix}}/include
|
||||
|
||||
Name: {name}
|
||||
Description: {description}
|
||||
URL: {url}
|
||||
Version: {version}
|
||||
Cflags: -I${{includedir}}
|
||||
Libs: -L${{libdir}} -ldeltachat
|
||||
Libs.private: {libs_priv}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
@@ -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
@@ -14,17 +14,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 +172,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 +186,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 +196,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 +206,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 +226,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 +334,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,12 +388,21 @@ 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,
|
||||
b"CLI\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
|
||||
unsafe { dc_cmdline_skip_auth() };
|
||||
|
||||
if args.len() == 2 {
|
||||
if unsafe { !dc_open(&mut context, &args[1], None) } {
|
||||
if 0 == unsafe {
|
||||
let a = to_cstring(&args[1]);
|
||||
let res = dc_open(&mut context, a, 0 as *const _);
|
||||
free(a as *mut _);
|
||||
res
|
||||
} {
|
||||
println!("Error: Cannot open {}.", args[0],);
|
||||
}
|
||||
} else if args.len() != 1 {
|
||||
@@ -448,6 +460,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(())
|
||||
@@ -466,7 +484,7 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
let arg1_c = if arg1.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
arg1.strdup()
|
||||
to_cstring(arg1)
|
||||
};
|
||||
|
||||
match arg0 {
|
||||
@@ -480,19 +498,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 +560,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)?,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
extern crate deltachat;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{thread, time};
|
||||
use tempfile::tempdir;
|
||||
|
||||
use deltachat::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_chatlist::*;
|
||||
use deltachat::dc_configure::*;
|
||||
use deltachat::dc_contact::*;
|
||||
use deltachat::dc_job::{
|
||||
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
|
||||
dc_perform_smtp_jobs,
|
||||
};
|
||||
use deltachat::dc_lot::*;
|
||||
|
||||
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
|
||||
println!("[{:?}]", event);
|
||||
@@ -39,7 +41,7 @@ extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> us
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = dc_get_info(&ctx);
|
||||
let info_s = CStr::from_ptr(info);
|
||||
@@ -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,46 +68,59 @@ 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
|
||||
|
||||
println!("opening database {:?}", dbfile);
|
||||
|
||||
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
|
||||
assert_eq!(dc_open(&ctx, dbfile.as_ptr(), std::ptr::null()), 1);
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
let pw = args[1].clone();
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
||||
.unwrap();
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
||||
configure(&ctx);
|
||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"));
|
||||
ctx.set_config(config::Config::MailPw, Some(&pw));
|
||||
dc_configure(&ctx);
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
let email = CString::new("dignifiedquire@gmail.com").unwrap();
|
||||
println!("sending a message");
|
||||
let contact_id =
|
||||
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
||||
let 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 contact_id = dc_create_contact(&ctx, std::ptr::null(), email.as_ptr());
|
||||
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
|
||||
let msg_text = CString::new("Hi, here is my first message!").unwrap();
|
||||
dc_send_text_msg(&ctx, chat_id, msg_text.as_ptr());
|
||||
|
||||
println!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
||||
let chats = dc_get_chatlist(&ctx, 0, std::ptr::null(), 0);
|
||||
|
||||
for i in 0..chats.len() {
|
||||
let summary = chats.get_summary(0, None);
|
||||
let text1 = summary.get_text1();
|
||||
let text2 = summary.get_text2();
|
||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||
for i in 0..dc_chatlist_get_cnt(chats) {
|
||||
let summary = dc_chatlist_get_summary(chats, 0, std::ptr::null_mut());
|
||||
let text1 = dc_lot_get_text1(summary);
|
||||
let text2 = dc_lot_get_text2(summary);
|
||||
|
||||
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);
|
||||
}
|
||||
dc_chatlist_unref(chats);
|
||||
|
||||
thread::sleep(duration);
|
||||
|
||||
@@ -122,8 +137,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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,6 +11,8 @@ high level API reference
|
||||
- :class:`deltachat.chatting.Contact`
|
||||
- :class:`deltachat.chatting.Chat`
|
||||
- :class:`deltachat.message.Message`
|
||||
- :class:`deltachat.message.MessageType`
|
||||
- :class:`deltachat.message.MessageState`
|
||||
|
||||
Account
|
||||
-------
|
||||
@@ -37,3 +39,16 @@ Message
|
||||
.. autoclass:: deltachat.message.Message
|
||||
:members:
|
||||
|
||||
MessageType
|
||||
------------
|
||||
|
||||
.. autoclass:: deltachat.message.MessageType
|
||||
:members:
|
||||
|
||||
MessageState
|
||||
------------
|
||||
|
||||
.. autoclass:: deltachat.message.MessageState
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
@@ -288,6 +288,10 @@ intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
autodoc_member_order = "bysource"
|
||||
# always document __init__ functions
|
||||
def skip(app, what, name, obj, skip, options):
|
||||
import attr
|
||||
if name == "__init__":
|
||||
if not hasattr(obj.im_class, "__attrs_attrs__"):
|
||||
return False
|
||||
return skip
|
||||
|
||||
def setup(app):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -17,7 +17,7 @@ def main():
|
||||
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
|
||||
long_description=long_description,
|
||||
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors',
|
||||
install_requires=['cffi>=1.0.0', 'six'],
|
||||
install_requires=['cffi>=1.0.0', 'attrs', 'six'],
|
||||
packages=setuptools.find_packages('src'),
|
||||
package_dir={'': 'src'},
|
||||
cffi_modules=['src/deltachat/_build.py:ffibuilder'],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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':
|
||||
@@ -36,7 +31,6 @@ def ffibuilder():
|
||||
libs = ['deltachat']
|
||||
objs = []
|
||||
incs = []
|
||||
extra_link_args = []
|
||||
builder = cffi.FFI()
|
||||
builder.set_source(
|
||||
'deltachat.capi',
|
||||
@@ -76,8 +70,8 @@ def ffibuilder():
|
||||
distutils.sysconfig.customize_compiler(cc)
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
src_name = os.path.join(tmpdir, "include.h")
|
||||
dst_name = os.path.join(tmpdir, "expanded.h")
|
||||
src_name = os.path.join(tmpdir, "prep.h")
|
||||
dst_name = os.path.join(tmpdir, "prep2.c")
|
||||
with open(src_name, "w") as src_fp:
|
||||
src_fp.write('#include <deltachat.h>')
|
||||
cc.preprocess(source=src_name,
|
||||
|
||||
@@ -7,9 +7,9 @@ import re
|
||||
import time
|
||||
from array import array
|
||||
try:
|
||||
from queue import Queue, Empty
|
||||
from queue import Queue
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty
|
||||
from Queue import Queue
|
||||
|
||||
import deltachat
|
||||
from . import const
|
||||
@@ -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:
|
||||
@@ -143,6 +142,15 @@ class Account(object):
|
||||
self.check_is_configured()
|
||||
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
|
||||
|
||||
def create_message(self, view_type):
|
||||
""" create a new non persistent message.
|
||||
|
||||
:param view_type: a string specifying "text", "video",
|
||||
"image", "audio" or "file".
|
||||
:returns: :class:`deltachat.message.Message` instance.
|
||||
"""
|
||||
return Message.new(self._dc_context, view_type)
|
||||
|
||||
def create_contact(self, email, name=None):
|
||||
""" create a (new) Contact. If there already is a Contact
|
||||
with that e-mail address, it is unblocked and its name is
|
||||
@@ -158,17 +166,6 @@ class Account(object):
|
||||
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return Contact(self._dc_context, contact_id)
|
||||
|
||||
def delete_contact(self, contact):
|
||||
""" delete a Contact.
|
||||
|
||||
:param contact: contact object obtained
|
||||
:returns: True if deletion succeeded (contact was deleted)
|
||||
"""
|
||||
contact_id = contact.id
|
||||
assert contact._dc_context == self._dc_context
|
||||
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
|
||||
|
||||
def get_contacts(self, query=None, with_self=False, only_verified=False):
|
||||
""" get a (filtered) list of contacts.
|
||||
|
||||
@@ -176,7 +173,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)
|
||||
@@ -204,7 +201,7 @@ class Account(object):
|
||||
assert isinstance(contact, int)
|
||||
contact_id = contact
|
||||
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
|
||||
return Chat(self, chat_id)
|
||||
return Chat(self._dc_context, chat_id)
|
||||
|
||||
def create_chat_by_message(self, message):
|
||||
""" create or get an existing chat object for the
|
||||
@@ -221,7 +218,7 @@ class Account(object):
|
||||
assert isinstance(message, int)
|
||||
msg_id = message
|
||||
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
|
||||
return Chat(self, chat_id)
|
||||
return Chat(self._dc_context, chat_id)
|
||||
|
||||
def create_group_chat(self, name, verified=False):
|
||||
""" create a new group chat object.
|
||||
@@ -233,7 +230,7 @@ class Account(object):
|
||||
"""
|
||||
bytes_name = name.encode("utf8")
|
||||
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
|
||||
return Chat(self, chat_id)
|
||||
return Chat(self._dc_context, chat_id)
|
||||
|
||||
def get_chats(self):
|
||||
""" return list of chats.
|
||||
@@ -249,15 +246,15 @@ class Account(object):
|
||||
chatlist = []
|
||||
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
|
||||
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
|
||||
chatlist.append(Chat(self, chat_id))
|
||||
chatlist.append(Chat(self._dc_context, chat_id))
|
||||
return chatlist
|
||||
|
||||
def get_deaddrop_chat(self):
|
||||
return Chat(self, const.DC_CHAT_ID_DEADDROP)
|
||||
return Chat(self._dc_context, const.DC_CHAT_ID_DEADDROP)
|
||||
|
||||
def get_message_by_id(self, msg_id):
|
||||
""" return Message instance. """
|
||||
return Message.from_db(self, msg_id)
|
||||
return Message.from_db(self._dc_context, msg_id)
|
||||
|
||||
def mark_seen_messages(self, messages):
|
||||
""" mark the given set of messages as seen.
|
||||
@@ -440,17 +437,6 @@ class EventLogger:
|
||||
raise ValueError("{}({!r},{!r})".format(*ev))
|
||||
return ev
|
||||
|
||||
def ensure_event_not_queued(self, event_name_regex):
|
||||
__tracebackhide__ = True
|
||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||
while 1:
|
||||
try:
|
||||
ev = self._event_queue.get(False)
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
assert not rex.match(ev[0]), "event found {}".format(ev)
|
||||
|
||||
def get_matching(self, event_name_regex, check_error=True):
|
||||
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
""" chatting related objects: Contact, Chat, Message. """
|
||||
|
||||
import mimetypes
|
||||
import os
|
||||
|
||||
from . import props
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||
from .capi import lib, ffi
|
||||
from . import const
|
||||
import attr
|
||||
from attr import validators as v
|
||||
from .message import Message
|
||||
|
||||
|
||||
@attr.s
|
||||
class Contact(object):
|
||||
""" Delta-Chat Contact.
|
||||
|
||||
You obtain instances of it through :class:`deltachat.account.Account`.
|
||||
"""
|
||||
def __init__(self, dc_context, id):
|
||||
self._dc_context = dc_context
|
||||
self.id = id
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._dc_context == other._dc_context and self.id == other.id
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
|
||||
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
|
||||
id = attr.ib(validator=v.instance_of(int))
|
||||
|
||||
@property
|
||||
def _dc_contact(self):
|
||||
@@ -52,26 +46,14 @@ class Contact(object):
|
||||
return lib.dc_contact_is_verified(self._dc_contact)
|
||||
|
||||
|
||||
@attr.s
|
||||
class Chat(object):
|
||||
""" Chat object which manages members and through which you can send and retrieve messages.
|
||||
|
||||
You obtain instances of it through :class:`deltachat.account.Account`.
|
||||
"""
|
||||
|
||||
def __init__(self, account, id):
|
||||
self.account = account
|
||||
self._dc_context = account._dc_context
|
||||
self.id = id
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == getattr(other, "id", None) and \
|
||||
self._dc_context == getattr(other, "_dc_context", None)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Chat id={} name={} dc_context={}>".format(self.id, self.get_name(), self._dc_context)
|
||||
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
|
||||
id = attr.ib(validator=v.instance_of(int))
|
||||
|
||||
@property
|
||||
def _dc_chat(self):
|
||||
@@ -144,7 +126,7 @@ class Chat(object):
|
||||
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
|
||||
if msg_id == 0:
|
||||
raise ValueError("message could not be send, does chat exist?")
|
||||
return Message.from_db(self.account, msg_id)
|
||||
return Message.from_db(self._dc_context, msg_id)
|
||||
|
||||
def send_file(self, path, mime_type="application/octet-stream"):
|
||||
""" send a file and return the resulting Message instance.
|
||||
@@ -154,9 +136,14 @@ class Chat(object):
|
||||
:raises ValueError: if message can not be send/chat does not exist.
|
||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||
"""
|
||||
msg = self.prepare_message_file(path=path, mime_type=mime_type)
|
||||
self.send_prepared(msg)
|
||||
return msg
|
||||
path = as_dc_charpointer(path)
|
||||
mtype = as_dc_charpointer(mime_type)
|
||||
msg = Message.new(self._dc_context, "file")
|
||||
msg.set_file(path, mtype)
|
||||
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if msg_id == 0:
|
||||
raise ValueError("message could not be send, does chat exist?")
|
||||
return Message.from_db(self._dc_context, msg_id)
|
||||
|
||||
def send_image(self, path):
|
||||
""" send an image message and return the resulting Message instance.
|
||||
@@ -165,25 +152,14 @@ class Chat(object):
|
||||
:raises ValueError: if message can not be send/chat does not exist.
|
||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||
"""
|
||||
mime_type = mimetypes.guess_type(path)[0]
|
||||
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
|
||||
self.send_prepared(msg)
|
||||
return msg
|
||||
if not os.path.exists(path):
|
||||
raise ValueError("path does not exist: {!r}".format(path))
|
||||
msg = Message.new(self._dc_context, "image")
|
||||
msg.set_file(path)
|
||||
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
return Message.from_db(self._dc_context, msg_id)
|
||||
|
||||
def prepare_message(self, msg):
|
||||
""" create a new prepared message.
|
||||
|
||||
:param msg: the message to be prepared.
|
||||
:returns: :class:`deltachat.message.Message` instance.
|
||||
"""
|
||||
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if msg_id == 0:
|
||||
raise ValueError("message could not be prepared")
|
||||
# invalidate passed in message which is not safe to use anymore
|
||||
msg._dc_msg = msg.id = None
|
||||
return Message.from_db(self.account, msg_id)
|
||||
|
||||
def prepare_message_file(self, path, mime_type=None, view_type="file"):
|
||||
def prepare_file(self, path, mime_type=None, view_type="file"):
|
||||
""" prepare a message for sending and return the resulting Message instance.
|
||||
|
||||
To actually send the message, call :meth:`send_prepared`.
|
||||
@@ -191,13 +167,18 @@ class Chat(object):
|
||||
|
||||
:param path: path to the file.
|
||||
:param mime_type: the mime-type of this file, defaults to auto-detection.
|
||||
:param view_type: "text", "image", "gif", "audio", "video", "file"
|
||||
:param view_type: passed to :meth:`MessageType.new`.
|
||||
:raises ValueError: if message can not be prepared/chat does not exist.
|
||||
:returns: the resulting :class:`Message` instance
|
||||
"""
|
||||
msg = Message.new_empty(self.account, view_type)
|
||||
msg.set_file(path, mime_type)
|
||||
return self.prepare_message(msg)
|
||||
path = as_dc_charpointer(path)
|
||||
mtype = as_dc_charpointer(mime_type)
|
||||
msg = Message.new(self._dc_context, view_type)
|
||||
msg.set_file(path, mtype)
|
||||
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if msg_id == 0:
|
||||
raise ValueError("message could not be prepared, does chat exist?")
|
||||
return Message.from_db(self._dc_context, msg_id)
|
||||
|
||||
def send_prepared(self, message):
|
||||
""" send a previously prepared message.
|
||||
@@ -205,42 +186,12 @@ class Chat(object):
|
||||
:param message: a :class:`Message` instance previously returned by
|
||||
:meth:`prepare_file`.
|
||||
:raises ValueError: if message can not be sent.
|
||||
:returns: a :class:`deltachat.message.Message` instance as sent out.
|
||||
:returns: a :class:`deltachat.message.Message` instance with updated state
|
||||
"""
|
||||
assert message.id != 0 and message.is_out_preparing()
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
msg = Message.from_db(self.account, message.id)
|
||||
|
||||
# pass 0 as chat-id because core-docs say it's ok when out-preparing
|
||||
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg)
|
||||
if msg_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
assert sent_id == msg.id
|
||||
# modify message in place to avoid bad state for the caller
|
||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
||||
|
||||
def set_draft(self, message):
|
||||
""" set message as draft.
|
||||
|
||||
:param message: a :class:`Message` instance
|
||||
:returns: None
|
||||
"""
|
||||
if message is None:
|
||||
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL)
|
||||
else:
|
||||
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg)
|
||||
|
||||
def get_draft(self):
|
||||
""" get draft message for this chat.
|
||||
|
||||
:param message: a :class:`Message` instance
|
||||
:returns: Message object or None (if no draft available)
|
||||
"""
|
||||
x = lib.dc_get_draft(self._dc_context, self.id)
|
||||
if x == ffi.NULL:
|
||||
return None
|
||||
dc_msg = ffi.gc(x, lib.dc_msg_unref)
|
||||
return Message(self.account, dc_msg)
|
||||
return Message.from_db(self._dc_context, msg_id)
|
||||
|
||||
def get_messages(self):
|
||||
""" return list of messages in this chat.
|
||||
@@ -251,7 +202,7 @@ class Chat(object):
|
||||
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
|
||||
lib.dc_array_unref
|
||||
)
|
||||
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
|
||||
return list(iter_array(dc_array, lambda x: Message.from_db(self._dc_context, x)))
|
||||
|
||||
def count_fresh_messages(self):
|
||||
""" return number of fresh messages in this chat.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,55 +1,59 @@
|
||||
""" chatting related objects: Contact, Chat, Message. """
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from . import props
|
||||
from .cutil import from_dc_charpointer, as_dc_charpointer
|
||||
from .capi import lib, ffi
|
||||
from . import const
|
||||
from datetime import datetime
|
||||
import attr
|
||||
from attr import validators as v
|
||||
|
||||
|
||||
@attr.s
|
||||
class Message(object):
|
||||
""" Message object.
|
||||
|
||||
You obtain instances of it through :class:`deltachat.account.Account` or
|
||||
:class:`deltachat.chatting.Chat`.
|
||||
"""
|
||||
def __init__(self, account, dc_msg):
|
||||
self.account = account
|
||||
self._dc_context = account._dc_context
|
||||
assert isinstance(self._dc_context, ffi.CData)
|
||||
assert isinstance(dc_msg, ffi.CData)
|
||||
assert dc_msg != ffi.NULL
|
||||
self._dc_msg = dc_msg
|
||||
self.id = lib.dc_msg_get_id(dc_msg)
|
||||
assert self.id is not None and self.id >= 0, repr(self.id)
|
||||
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
|
||||
try:
|
||||
id = attr.ib(validator=v.instance_of((int, long)))
|
||||
except NameError: # py35
|
||||
id = attr.ib(validator=v.instance_of(int))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.account == other.account and self.id == other.id
|
||||
|
||||
def __repr__(self):
|
||||
return "<Message id={} dc_context={}>".format(self.id, self._dc_context)
|
||||
|
||||
@classmethod
|
||||
def from_db(cls, account, id):
|
||||
assert id > 0
|
||||
return cls(account, ffi.gc(
|
||||
lib.dc_get_msg(account._dc_context, id),
|
||||
@property
|
||||
def _dc_msg(self):
|
||||
if self.id > 0:
|
||||
return ffi.gc(
|
||||
lib.dc_get_msg(self._dc_context, self.id),
|
||||
lib.dc_msg_unref
|
||||
))
|
||||
)
|
||||
return self._dc_msg_volatile
|
||||
|
||||
@classmethod
|
||||
def new_empty(cls, account, view_type):
|
||||
""" create a non-persistent message.
|
||||
def from_db(cls, _dc_context, id):
|
||||
assert id > 0
|
||||
return cls(_dc_context, id)
|
||||
|
||||
:param: view_type is "text", "audio", "video", "file"
|
||||
"""
|
||||
view_type_code = get_viewtype_code_from_name(view_type)
|
||||
return Message(account, ffi.gc(
|
||||
lib.dc_msg_new(account._dc_context, view_type_code),
|
||||
@classmethod
|
||||
def new(cls, dc_context, view_type):
|
||||
""" create a non-persistent method. """
|
||||
msg = cls(dc_context, 0)
|
||||
view_type_code = MessageType.get_typecode(view_type)
|
||||
msg._dc_msg_volatile = ffi.gc(
|
||||
lib.dc_msg_new(dc_context, view_type_code),
|
||||
lib.dc_msg_unref
|
||||
))
|
||||
)
|
||||
return msg
|
||||
|
||||
def get_state(self):
|
||||
""" get the message in/out state.
|
||||
|
||||
:returns: :class:`deltachat.message.MessageState`
|
||||
"""
|
||||
return MessageState(self)
|
||||
|
||||
@props.with_doc
|
||||
def text(self):
|
||||
@@ -58,9 +62,7 @@ class Message(object):
|
||||
|
||||
def set_text(self, text):
|
||||
"""set text of this message. """
|
||||
assert self.id > 0, "message not prepared"
|
||||
assert self.is_out_preparing()
|
||||
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
||||
return lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
||||
|
||||
@props.with_doc
|
||||
def filename(self):
|
||||
@@ -68,23 +70,9 @@ class Message(object):
|
||||
return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg))
|
||||
|
||||
def set_file(self, path, mime_type=None):
|
||||
"""set file for this message from path and mime_type. """
|
||||
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
|
||||
if not os.path.exists(path):
|
||||
raise ValueError("path does not exist: {!r}".format(path))
|
||||
blobdir = self.account.get_blobdir()
|
||||
if not path.startswith(blobdir):
|
||||
for i in range(50):
|
||||
ext = "" if i == 0 else "-" + str(i)
|
||||
dest = os.path.join(blobdir, os.path.basename(path) + ext)
|
||||
if os.path.exists(dest):
|
||||
continue
|
||||
shutil.copyfile(path, dest)
|
||||
break
|
||||
else:
|
||||
raise ValueError("could not create blobdir-path for {}".format(path))
|
||||
path = dest
|
||||
assert path.startswith(blobdir), path
|
||||
"""set file for this message. """
|
||||
mtype = ffi.NULL if mime_type is None else mime_type
|
||||
assert os.path.exists(path)
|
||||
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
|
||||
|
||||
@props.with_doc
|
||||
@@ -97,17 +85,18 @@ class Message(object):
|
||||
"""mime type of the file (if it exists)"""
|
||||
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
|
||||
|
||||
@props.with_doc
|
||||
def view_type(self):
|
||||
"""the view type of this message.
|
||||
|
||||
:returns: a :class:`deltachat.message.MessageType` instance.
|
||||
"""
|
||||
return MessageType(lib.dc_msg_get_viewtype(self._dc_msg))
|
||||
|
||||
def is_setup_message(self):
|
||||
""" return True if this message is a setup message. """
|
||||
return lib.dc_msg_is_setupmessage(self._dc_msg)
|
||||
|
||||
def get_message_info(self):
|
||||
""" Return informational text for a single message.
|
||||
|
||||
The text is multiline and may contain eg. the raw text of the message.
|
||||
"""
|
||||
return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id))
|
||||
|
||||
def continue_key_transfer(self, setup_code):
|
||||
""" extract key and use it as primary key for this account. """
|
||||
lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code))
|
||||
@@ -142,7 +131,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)
|
||||
@@ -155,7 +144,7 @@ class Message(object):
|
||||
"""
|
||||
from .chatting import Chat
|
||||
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
|
||||
return Chat(self.account, chat_id)
|
||||
return Chat(self._dc_context, chat_id)
|
||||
|
||||
def get_sender_contact(self):
|
||||
"""return the contact of who wrote the message.
|
||||
@@ -166,20 +155,66 @@ class Message(object):
|
||||
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
||||
return Contact(self._dc_context, contact_id)
|
||||
|
||||
#
|
||||
# Message State query methods
|
||||
#
|
||||
|
||||
@attr.s
|
||||
class MessageType(object):
|
||||
""" DeltaChat message type, with is_* methods. """
|
||||
_type = attr.ib(validator=v.instance_of(int))
|
||||
_mapping = {
|
||||
const.DC_MSG_TEXT: 'text',
|
||||
const.DC_MSG_IMAGE: 'image',
|
||||
const.DC_MSG_GIF: 'gif',
|
||||
const.DC_MSG_AUDIO: 'audio',
|
||||
const.DC_MSG_VIDEO: 'video',
|
||||
const.DC_MSG_FILE: 'file'
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_typecode(cls, view_type):
|
||||
for code, value in cls._mapping.items():
|
||||
if value == view_type:
|
||||
return code
|
||||
raise ValueError("message typecode not found for {!r}".format(view_type))
|
||||
|
||||
@props.with_doc
|
||||
def name(self):
|
||||
""" human readable type name. """
|
||||
return self._mapping.get(self._type, "")
|
||||
|
||||
def is_text(self):
|
||||
""" return True if it's a text message. """
|
||||
return self._type == const.DC_MSG_TEXT
|
||||
|
||||
def is_image(self):
|
||||
""" return True if it's an image message. """
|
||||
return self._type == const.DC_MSG_IMAGE
|
||||
|
||||
def is_gif(self):
|
||||
""" return True if it's a gif message. """
|
||||
return self._type == const.DC_MSG_GIF
|
||||
|
||||
def is_audio(self):
|
||||
""" return True if it's an audio message. """
|
||||
return self._type == const.DC_MSG_AUDIO
|
||||
|
||||
def is_video(self):
|
||||
""" return True if it's a video message. """
|
||||
return self._type == const.DC_MSG_VIDEO
|
||||
|
||||
def is_file(self):
|
||||
""" return True if it's a file message. """
|
||||
return self._type == const.DC_MSG_FILE
|
||||
|
||||
|
||||
@attr.s
|
||||
class MessageState(object):
|
||||
""" Current Message In/Out state, updated on each call of is_* methods.
|
||||
"""
|
||||
message = attr.ib(validator=v.instance_of(Message))
|
||||
|
||||
@property
|
||||
def _msgstate(self):
|
||||
if self.id == 0:
|
||||
dc_msg = self.message._dc_msg
|
||||
else:
|
||||
# load message from db to get a fresh/current state
|
||||
dc_msg = ffi.gc(
|
||||
lib.dc_get_msg(self._dc_context, self.id),
|
||||
lib.dc_msg_unref
|
||||
)
|
||||
return lib.dc_msg_get_state(dc_msg)
|
||||
return lib.dc_msg_get_state(self.message._dc_msg)
|
||||
|
||||
def is_in_fresh(self):
|
||||
""" return True if Message is incoming fresh message (un-noticed).
|
||||
@@ -233,56 +268,3 @@ class Message(object):
|
||||
state, you'll receive the event DC_EVENT_MSG_READ.
|
||||
"""
|
||||
return self._msgstate == const.DC_STATE_OUT_MDN_RCVD
|
||||
|
||||
#
|
||||
# Message type query methods
|
||||
#
|
||||
|
||||
@property
|
||||
def _view_type(self):
|
||||
assert self.id > 0
|
||||
return lib.dc_msg_get_viewtype(self._dc_msg)
|
||||
|
||||
def is_text(self):
|
||||
""" return True if it's a text message. """
|
||||
return self._view_type == const.DC_MSG_TEXT
|
||||
|
||||
def is_image(self):
|
||||
""" return True if it's an image message. """
|
||||
return self._view_type == const.DC_MSG_IMAGE
|
||||
|
||||
def is_gif(self):
|
||||
""" return True if it's a gif message. """
|
||||
return self._view_type == const.DC_MSG_GIF
|
||||
|
||||
def is_audio(self):
|
||||
""" return True if it's an audio message. """
|
||||
return self._view_type == const.DC_MSG_AUDIO
|
||||
|
||||
def is_video(self):
|
||||
""" return True if it's a video message. """
|
||||
return self._view_type == const.DC_MSG_VIDEO
|
||||
|
||||
def is_file(self):
|
||||
""" return True if it's a file message. """
|
||||
return self._view_type == const.DC_MSG_FILE
|
||||
|
||||
|
||||
# some code for handling DC_MSG_* view types
|
||||
|
||||
_view_type_mapping = {
|
||||
const.DC_MSG_TEXT: 'text',
|
||||
const.DC_MSG_IMAGE: 'image',
|
||||
const.DC_MSG_GIF: 'gif',
|
||||
const.DC_MSG_AUDIO: 'audio',
|
||||
const.DC_MSG_VIDEO: 'video',
|
||||
const.DC_MSG_FILE: 'file'
|
||||
}
|
||||
|
||||
|
||||
def get_viewtype_code_from_name(view_type_name):
|
||||
for code, value in _view_type_mapping.items():
|
||||
if value == view_type_name:
|
||||
return code
|
||||
raise ValueError("message typecode not found for {!r}, "
|
||||
"available {!r}".format(view_type_name, list(_view_type_mapping.values())))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,14 +16,6 @@ def pytest_addoption(parser):
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
cfg = config.getoption('--liveconfig')
|
||||
if not cfg:
|
||||
cfg = os.getenv('DCC_PY_LIVECONFIG')
|
||||
if cfg:
|
||||
config.option.liveconfig = cfg
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_runtest_call(item):
|
||||
# perform early finalization because we otherwise get cloberred
|
||||
@@ -36,8 +28,6 @@ def pytest_runtest_call(item):
|
||||
|
||||
|
||||
def pytest_report_header(config, startdir):
|
||||
summary = []
|
||||
|
||||
t = tempfile.mktemp()
|
||||
try:
|
||||
ac = Account(t, eventlogging=False)
|
||||
@@ -45,19 +35,10 @@ def pytest_report_header(config, startdir):
|
||||
ac.shutdown()
|
||||
finally:
|
||||
os.remove(t)
|
||||
summary.extend(['Deltachat core={} sqlite={}'.format(
|
||||
info['deltachat_core_version'],
|
||||
info['sqlite_version'],
|
||||
)])
|
||||
|
||||
cfg = config.option.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))
|
||||
return summary
|
||||
return "Deltachat core={} sqlite={}".format(
|
||||
info['deltachat_core_version'],
|
||||
info['sqlite_version'],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@@ -73,56 +54,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 +70,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():
|
||||
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 +104,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
|
||||
|
||||
@@ -2,12 +2,11 @@ from __future__ import print_function
|
||||
import pytest
|
||||
import os
|
||||
from deltachat import const, Account
|
||||
from deltachat.message import Message
|
||||
from datetime import datetime, timedelta
|
||||
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
|
||||
|
||||
|
||||
class TestOfflineAccountBasic:
|
||||
class TestOfflineAccount:
|
||||
def test_wrong_db(self, tmpdir):
|
||||
p = tmpdir.join("hello.db")
|
||||
p.write("123")
|
||||
@@ -58,22 +57,16 @@ class TestOfflineAccountBasic:
|
||||
with pytest.raises(KeyError):
|
||||
ac1.get_config("123123")
|
||||
|
||||
|
||||
class TestOfflineContact:
|
||||
def test_contact_attr(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
|
||||
contact2 = ac1.create_contact(email="some1@hello.com", name="some1")
|
||||
str(contact1)
|
||||
repr(contact1)
|
||||
assert contact1 == contact2
|
||||
assert contact1.id
|
||||
assert contact1.addr == "some1@hello.com"
|
||||
assert contact1.display_name == "some1"
|
||||
assert not contact1.is_blocked()
|
||||
assert not contact1.is_verified()
|
||||
|
||||
def test_get_contacts_and_delete(self, acfactory):
|
||||
def test_get_contacts(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
|
||||
contacts = ac1.get_contacts()
|
||||
@@ -86,48 +79,26 @@ class TestOfflineContact:
|
||||
contacts = ac1.get_contacts(with_self=True)
|
||||
assert len(contacts) == 2
|
||||
|
||||
assert ac1.delete_contact(contact1)
|
||||
assert contact1 not in ac1.get_contacts()
|
||||
|
||||
def test_get_contacts_and_delete_fails(self, acfactory):
|
||||
def test_chat(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
chat.send_text("one messae")
|
||||
assert not ac1.delete_contact(contact1)
|
||||
|
||||
|
||||
class TestOfflineChat:
|
||||
@pytest.fixture
|
||||
def ac1(self, acfactory):
|
||||
return acfactory.get_configured_offline_account()
|
||||
|
||||
@pytest.fixture
|
||||
def chat1(self, ac1):
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
||||
return chat
|
||||
|
||||
def test_display(self, chat1):
|
||||
str(chat1)
|
||||
repr(chat1)
|
||||
|
||||
def test_chat_idempotent(self, chat1, ac1):
|
||||
contact1 = chat1.get_contacts()[0]
|
||||
chat2 = ac1.create_chat_by_contact(contact1.id)
|
||||
assert chat2.id == chat1.id
|
||||
assert chat2.get_name() == chat1.get_name()
|
||||
assert chat1 == chat2
|
||||
assert not (chat1 != chat2)
|
||||
assert chat2.id == chat.id
|
||||
assert chat2.get_name() == chat.get_name()
|
||||
assert chat == chat2
|
||||
assert not (chat != chat2)
|
||||
|
||||
for ichat in ac1.get_chats():
|
||||
if ichat.id == chat1.id:
|
||||
if ichat.id == chat.id:
|
||||
break
|
||||
else:
|
||||
pytest.fail("could not find chat")
|
||||
|
||||
def test_group_chat_creation(self, ac1):
|
||||
def test_group_chat_creation(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
contact2 = ac1.create_contact("some2@hello.com", name="some2")
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
@@ -140,73 +111,64 @@ class TestOfflineChat:
|
||||
chat.set_name("title2")
|
||||
assert chat.get_name() == "title2"
|
||||
|
||||
def test_delete_and_send_fails(self, ac1, chat1):
|
||||
chat1.delete()
|
||||
def test_delete_and_send_fails(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
chat.delete()
|
||||
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
with pytest.raises(ValueError):
|
||||
chat1.send_text("msg1")
|
||||
chat.send_text("msg1")
|
||||
|
||||
def test_prepare_message_and_send(self, ac1, chat1):
|
||||
msg = chat1.prepare_message(Message.new_empty(chat1.account, "text"))
|
||||
msg.set_text("hello world")
|
||||
assert msg.text == "hello world"
|
||||
assert msg.id > 0
|
||||
chat1.send_prepared(msg)
|
||||
assert "Sent" in msg.get_message_info()
|
||||
str(msg)
|
||||
repr(msg)
|
||||
assert msg == ac1.get_message_by_id(msg.id)
|
||||
def test_create_message(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
message = ac1.create_message("text")
|
||||
assert message.id == 0
|
||||
assert message._dc_msg is message._dc_msg
|
||||
message.set_text("hello")
|
||||
assert message.text == "hello"
|
||||
assert message.id == 0
|
||||
|
||||
def test_prepare_file(self, ac1, chat1):
|
||||
blobdir = ac1.get_blobdir()
|
||||
p = os.path.join(blobdir, "somedata.txt")
|
||||
with open(p, "w") as f:
|
||||
f.write("some data")
|
||||
message = chat1.prepare_message_file(p)
|
||||
assert message.id > 0
|
||||
message.set_text("hello world")
|
||||
assert message.is_out_preparing()
|
||||
assert message.text == "hello world"
|
||||
chat1.send_prepared(message)
|
||||
assert "Sent" in message.get_message_info()
|
||||
|
||||
def test_message_eq_contains(self, chat1):
|
||||
msg = chat1.send_text("msg1")
|
||||
assert msg in chat1.get_messages()
|
||||
assert not (msg not in chat1.get_messages())
|
||||
str(msg)
|
||||
repr(msg)
|
||||
|
||||
def test_message_send_text(self, chat1):
|
||||
msg = chat1.send_text("msg1")
|
||||
def test_message(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
msg = chat.send_text("msg1")
|
||||
assert msg
|
||||
assert msg.is_text()
|
||||
assert not msg.is_audio()
|
||||
assert not msg.is_video()
|
||||
assert not msg.is_gif()
|
||||
assert not msg.is_file()
|
||||
assert not msg.is_image()
|
||||
assert msg.view_type.is_text()
|
||||
assert msg.view_type.name == "text"
|
||||
assert not msg.view_type.is_audio()
|
||||
assert not msg.view_type.is_video()
|
||||
assert not msg.view_type.is_gif()
|
||||
assert not msg.view_type.is_file()
|
||||
assert not msg.view_type.is_image()
|
||||
msg_state = msg.get_state()
|
||||
assert not msg_state.is_in_fresh()
|
||||
assert not msg_state.is_in_noticed()
|
||||
assert not msg_state.is_in_seen()
|
||||
assert msg_state.is_out_pending()
|
||||
assert not msg_state.is_out_failed()
|
||||
assert not msg_state.is_out_delivered()
|
||||
assert not msg_state.is_out_mdn_received()
|
||||
|
||||
assert not msg.is_in_fresh()
|
||||
assert not msg.is_in_noticed()
|
||||
assert not msg.is_in_seen()
|
||||
assert msg.is_out_pending()
|
||||
assert not msg.is_out_failed()
|
||||
assert not msg.is_out_delivered()
|
||||
assert not msg.is_out_mdn_received()
|
||||
def test_create_chat_by_mssage_id(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
msg = chat.send_text("msg1")
|
||||
assert chat == ac1.create_chat_by_message(msg)
|
||||
assert chat == ac1.create_chat_by_message(msg.id)
|
||||
|
||||
def test_create_chat_by_message_id(self, ac1, chat1):
|
||||
msg = chat1.send_text("msg1")
|
||||
assert chat1 == ac1.create_chat_by_message(msg)
|
||||
assert chat1 == ac1.create_chat_by_message(msg.id)
|
||||
|
||||
def test_message_image(self, chat1, data, lp):
|
||||
def test_message_image(self, acfactory, data, lp):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
with pytest.raises(ValueError):
|
||||
chat1.send_image(path="notexists")
|
||||
chat.send_image(path="notexists")
|
||||
fn = data.get_path("d.png")
|
||||
lp.sec("sending image")
|
||||
msg = chat1.send_image(fn)
|
||||
assert msg.is_image()
|
||||
msg = chat.send_image(fn)
|
||||
assert msg.view_type.name == "image"
|
||||
assert msg
|
||||
assert msg.id > 0
|
||||
assert os.path.exists(msg.filename)
|
||||
@@ -217,19 +179,20 @@ class TestOfflineChat:
|
||||
("text/plain", "text/plain"),
|
||||
("image/png", "image/png"),
|
||||
])
|
||||
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
|
||||
def test_message_file(self, acfactory, data, lp, typein, typeout):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
lp.sec("sending file")
|
||||
fn = data.get_path("r.txt")
|
||||
msg = chat1.send_file(fn, typein)
|
||||
msg = chat.send_file(fn, typein)
|
||||
assert msg
|
||||
assert msg.id > 0
|
||||
assert msg.is_file()
|
||||
assert msg.view_type.name == "file"
|
||||
assert msg.view_type.is_file()
|
||||
assert os.path.exists(msg.filename)
|
||||
assert msg.filename.endswith(msg.basename)
|
||||
assert msg.filemime == typeout
|
||||
msg2 = chat1.send_file(fn, typein)
|
||||
assert msg2 != msg
|
||||
assert msg2.filename != msg.filename
|
||||
|
||||
def test_create_chat_mismatch(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
@@ -242,9 +205,12 @@ class TestOfflineChat:
|
||||
with pytest.raises(ValueError):
|
||||
ac2.create_chat_by_message(msg)
|
||||
|
||||
def test_chat_message_distinctions(self, ac1, chat1):
|
||||
def test_chat_message_distinctions(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
past1s = datetime.utcnow() - timedelta(seconds=1)
|
||||
msg = chat1.send_text("msg1")
|
||||
msg = chat.send_text("msg1")
|
||||
ts = msg.time_sent
|
||||
assert msg.time_received is None
|
||||
assert ts.strftime("Y")
|
||||
@@ -252,7 +218,8 @@ class TestOfflineChat:
|
||||
contact = msg.get_sender_contact()
|
||||
assert contact == ac1.get_self_contact()
|
||||
|
||||
def test_basic_configure_ok_addr_setting_forbidden(self, ac1):
|
||||
def test_basic_configure_ok_addr_setting_forbidden(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
assert ac1.get_config("mail_pw")
|
||||
assert ac1.is_configured()
|
||||
with pytest.raises(ValueError):
|
||||
@@ -291,21 +258,11 @@ class TestOfflineChat:
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
|
||||
def test_ac_setup_message_fails(self, ac1):
|
||||
def test_ac_setup_message_fails(self, acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
with pytest.raises(RuntimeError):
|
||||
ac1.initiate_key_transfer()
|
||||
|
||||
def test_set_get_draft(self, chat1):
|
||||
msg = Message.new_empty(chat1.account, "text")
|
||||
msg1 = chat1.prepare_message(msg)
|
||||
msg1.set_text("hello")
|
||||
chat1.set_draft(msg1)
|
||||
msg1.set_text("obsolete")
|
||||
msg2 = chat1.get_draft()
|
||||
assert msg2.text == "hello"
|
||||
chat1.set_draft(None)
|
||||
assert chat1.get_draft() is None
|
||||
|
||||
|
||||
class TestOnlineAccount:
|
||||
def test_one_account_init(self, acfactory):
|
||||
@@ -326,7 +283,7 @@ class TestOnlineAccount:
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[1] == msg_out.id
|
||||
|
||||
def test_two_accounts_send_receive(self, acfactory):
|
||||
def test_two_acocunts_send_receive(self, acfactory):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
@@ -394,7 +351,7 @@ class TestOnlineAccount:
|
||||
evt_name, data1, data2 = ev
|
||||
assert data1 == chat.id
|
||||
assert data2 == msg_out.id
|
||||
assert msg_out.is_out_delivered()
|
||||
assert msg_out.get_state().is_out_delivered()
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
@@ -421,42 +378,10 @@ class TestOnlineAccount:
|
||||
lp.sec("mark message as seen on ac2, wait for changes on ac1")
|
||||
ac2.mark_seen_messages([msg_in])
|
||||
lp.step("1")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
||||
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
|
||||
ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
||||
lp.step("2")
|
||||
assert msg_out.is_out_mdn_received()
|
||||
|
||||
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"
|
||||
# ac1._evlogger.get_info_matching("Message marked as seen")
|
||||
assert msg_out.get_state().is_out_mdn_received()
|
||||
|
||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||
lp.sec("starting accounts, waiting for configuration")
|
||||
@@ -496,13 +421,13 @@ class TestOnlineAccount:
|
||||
evt_name, data1, data2 = ev
|
||||
assert data1 == chat.id
|
||||
assert data2 == msg_out.id
|
||||
assert msg_out.is_out_delivered()
|
||||
assert msg_out.get_state().is_out_delivered()
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev[2] == msg_out.id
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.is_image()
|
||||
assert msg_in.view_type.is_image()
|
||||
assert os.path.exists(msg_in.filename)
|
||||
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
||||
|
||||
@@ -538,7 +463,7 @@ class TestOnlineAccount:
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
||||
setup_code = ac1.initiate_key_transfer()
|
||||
ac2._evlogger.set_timeout(30)
|
||||
ac2._evlogger.set_timeout(10)
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev[2])
|
||||
assert msg.is_setup_message()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import shutil
|
||||
from filecmp import cmp
|
||||
from deltachat import const
|
||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||
@@ -11,15 +13,18 @@ class TestInCreation:
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
|
||||
blobdir = ac1.get_blobdir()
|
||||
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
||||
|
||||
lp.sec("create a message with a file in creation")
|
||||
path = data.get_path("d.png")
|
||||
prepared_original = chat.prepare_message_file(path)
|
||||
assert prepared_original.is_out_preparing()
|
||||
path = os.path.join(blobdir, "d.png")
|
||||
open(path, 'a').close()
|
||||
prepared_original = chat.prepare_file(path)
|
||||
assert prepared_original.get_state().is_out_preparing()
|
||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||
|
||||
lp.sec("forward the message while still in creation")
|
||||
@@ -32,36 +37,35 @@ class TestInCreation:
|
||||
forwarded_id = wait_msgs_changed(ac1, chat2.id)
|
||||
if forwarded_id == 0:
|
||||
forwarded_id = wait_msgs_changed(ac1, chat2.id)
|
||||
assert forwarded_id
|
||||
forwarded_msg = ac1.get_message_by_id(forwarded_id)
|
||||
assert forwarded_msg.is_out_preparing()
|
||||
assert forwarded_msg.get_state().is_out_preparing()
|
||||
|
||||
lp.sec("finish creating the file and send it")
|
||||
assert prepared_original.is_out_preparing()
|
||||
chat.send_prepared(prepared_original)
|
||||
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
|
||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||
shutil.copy(data.get_path("d.png"), path)
|
||||
sent_original = chat.send_prepared(prepared_original)
|
||||
assert sent_original.id == prepared_original.id
|
||||
state = sent_original.get_state()
|
||||
assert state.is_out_pending() or state.is_out_delivered()
|
||||
wait_msgs_changed(ac1, chat.id, sent_original.id)
|
||||
|
||||
lp.sec("expect the forwarded message to be sent now too")
|
||||
wait_msgs_changed(ac1, chat2.id, forwarded_id)
|
||||
fwd_msg = ac1.get_message_by_id(forwarded_id)
|
||||
assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered()
|
||||
state = ac1.get_message_by_id(forwarded_id).get_state()
|
||||
assert state.is_out_pending() or state.is_out_delivered()
|
||||
|
||||
lp.sec("wait for the messages to be delivered to SMTP")
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
||||
assert ev[1] == chat.id
|
||||
assert ev[2] == prepared_original.id
|
||||
assert ev[2] == sent_original.id
|
||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
||||
assert ev[1] == chat2.id
|
||||
assert ev[2] == forwarded_id
|
||||
|
||||
lp.sec("wait1 for original or forwarded messages to arrive")
|
||||
lp.sec("wait for both messages to arrive")
|
||||
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
received_original = ac2.get_message_by_id(ev1[2])
|
||||
assert cmp(received_original.filename, path, False)
|
||||
|
||||
lp.sec("wait2 for original or forwarded messages to arrive")
|
||||
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev2[1] != ev1[1]
|
||||
|
||||
@@ -64,14 +64,3 @@ def test_sig():
|
||||
assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2
|
||||
assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2
|
||||
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2
|
||||
|
||||
|
||||
def test_markseen_invalid_message_ids(acfactory):
|
||||
ac1 = acfactory.get_configured_offline_account()
|
||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
||||
chat = ac1.create_chat_by_contact(contact1)
|
||||
chat.send_text("one messae")
|
||||
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
msg_ids = [9]
|
||||
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
|
||||
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
|
||||
|
||||
@@ -8,18 +8,15 @@ envlist =
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
pytest -v -rsXx {posargs:tests}
|
||||
pytest -rsXx {posargs:tests}
|
||||
python tests/package_wheels.py {toxworkdir}/wheelhouse
|
||||
passenv =
|
||||
TRAVIS
|
||||
DCC_RS_DEV
|
||||
DCC_RS_TARGET
|
||||
DCC_PY_LIVECONFIG
|
||||
deps =
|
||||
pytest
|
||||
pytest-faulthandler
|
||||
pdbpp
|
||||
requests
|
||||
|
||||
[testenv:auditwheels]
|
||||
skipsdist = True
|
||||
@@ -52,7 +49,6 @@ commands =
|
||||
|
||||
|
||||
[pytest]
|
||||
addopts = -v -rs
|
||||
python_files = tests/test_*.py
|
||||
norecursedirs = .tox
|
||||
xfail_strict=true
|
||||
|
||||
@@ -23,10 +23,11 @@ if [ $? != 0 ]; then
|
||||
fi
|
||||
|
||||
pushd python
|
||||
if [ -e "./liveconfig" && -z "$DCC_PY_LIVECONFIG" ]; then
|
||||
export DCC_PY_LIVECONFIG=liveconfig
|
||||
toxargs="$@"
|
||||
if [ -e liveconfig ]; then
|
||||
toxargs="--liveconfig liveconfig $@"
|
||||
fi
|
||||
tox "$@"
|
||||
tox $toxargs
|
||||
ret=$?
|
||||
popd
|
||||
exit $ret
|
||||
|
||||
@@ -1 +1 @@
|
||||
nightly-2019-08-13
|
||||
nightly-2019-07-10
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{fmt, str};
|
||||
use mmime::mailimf_types::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::dc_contact::*;
|
||||
use crate::dc_tools::as_str;
|
||||
use crate::key::*;
|
||||
|
||||
@@ -94,7 +94,7 @@ impl Aheader {
|
||||
|
||||
match Self::from_str(value) {
|
||||
Ok(test) => {
|
||||
if addr_cmp(&test.addr, as_str(wanted_from)) {
|
||||
if dc_addr_cmp(&test.addr, as_str(wanted_from)) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
|
||||
1990
src/chat.rs
1990
src/chat.rs
File diff suppressed because it is too large
Load Diff
339
src/chatlist.rs
339
src/chatlist.rs
@@ -1,339 +0,0 @@
|
||||
use crate::chat::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::*;
|
||||
use crate::error::Result;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::*;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// An object representing a single chatlist in memory.
|
||||
///
|
||||
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
|
||||
/// The chatlist object is not updated; if you want an update, you have to recreate the object.
|
||||
///
|
||||
/// For a **typical chat overview**, the idea is to get the list of all chats via dc_get_chatlist()
|
||||
/// without any listflags (see below) and to implement a "virtual list" or so
|
||||
/// (the count of chats is known by chatlist.len()).
|
||||
///
|
||||
/// Only for the items that are in view (the list may have several hundreds chats),
|
||||
/// the UI should call chatlist.get_summary() then.
|
||||
/// chatlist.get_summary() provides all elements needed for painting the item.
|
||||
///
|
||||
/// On a click of such an item, the UI should change to the chat view
|
||||
/// and get all messages from this view via dc_get_chat_msgs().
|
||||
/// Again, a "virtual list" is created (the count of messages is known)
|
||||
/// and for each messages that is scrolled into view, dc_get_msg() is called then.
|
||||
///
|
||||
/// Why no listflags?
|
||||
/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed.
|
||||
/// The UI can just render these items differently then. Although the deaddrop link is currently always the
|
||||
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
|
||||
/// present and sorted into the list by date. Rendering the deaddrop in the described way
|
||||
/// would not add extra work in the UI then.
|
||||
pub struct Chatlist<'a> {
|
||||
context: &'a Context,
|
||||
/// Stores pairs of `chat_id, message_id`
|
||||
ids: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl<'a> Chatlist<'a> {
|
||||
pub fn get_context(&self) -> &Context {
|
||||
self.context
|
||||
}
|
||||
|
||||
/// Get a list of chats.
|
||||
/// The list can be filtered by query parameters.
|
||||
///
|
||||
/// The list is already sorted and starts with the most recent chat in use.
|
||||
/// The sorting takes care of invalid sending dates, drafts and chats without messages.
|
||||
/// Clients should not try to re-sort the list as this would be an expensive action
|
||||
/// and would result in inconsistencies between clients.
|
||||
///
|
||||
/// To get information about each entry, use eg. chatlist.get_summary().
|
||||
///
|
||||
/// By default, the function adds some special entries to the list.
|
||||
/// These special entries can be identified by the ID returned by chatlist.get_chat_id():
|
||||
/// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are
|
||||
/// messages from addresses that have no relationship to the configured account.
|
||||
/// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details
|
||||
/// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?"
|
||||
/// and offers the options "Yes" (call dc_create_chat_by_msg_id()), "Never" (call dc_block_contact())
|
||||
/// or "Not now".
|
||||
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
|
||||
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
|
||||
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
|
||||
/// "Show archived chats", if the user clicks this item, the UI should show a
|
||||
/// list of all archived chats that can be created by this function hen using
|
||||
/// the DC_GCL_ARCHIVED_ONLY flag.
|
||||
/// - DC_CHAT_ID_ALLDONE_HINT (7) - this special chat is present
|
||||
/// if DC_GCL_ADD_ALLDONE_HINT is added to listflags
|
||||
/// and if there are only archived chats.
|
||||
///
|
||||
/// The `listflags` is a combination of flags:
|
||||
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
|
||||
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
|
||||
/// chats
|
||||
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
||||
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
||||
/// not needed when DC_GCL_ARCHIVED_ONLY is already set)
|
||||
/// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
|
||||
/// is added as needed.
|
||||
/// `query`: An optional query for filtering the list. Only chats matching this query
|
||||
/// are returned.
|
||||
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
|
||||
/// are returned.
|
||||
pub fn try_load(
|
||||
context: &'a Context,
|
||||
listflags: usize,
|
||||
query: Option<&str>,
|
||||
query_contact_id: Option<u32>,
|
||||
) -> Result<Self> {
|
||||
let mut add_archived_link_item = 0;
|
||||
|
||||
// select with left join and minimum:
|
||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||
// which would refer the outer select and take a lot of time
|
||||
// - `GROUP BY` is needed several messages may have the same timestamp
|
||||
// - the list starts with the newest chats
|
||||
// nb: the query currently shows messages from blocked contacts in groups.
|
||||
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
||||
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
||||
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
||||
// shown at all permanent in the chatlist.
|
||||
|
||||
let process_row = |row: &rusqlite::Row| {
|
||||
let chat_id: i32 = row.get(0)?;
|
||||
// TODO: verify that it is okay for this to be Null
|
||||
let msg_id: i32 = row.get(1).unwrap_or_default();
|
||||
|
||||
Ok((chat_id as u32, msg_id as u32))
|
||||
};
|
||||
|
||||
let process_rows = |rows: rusqlite::MappedRows<_>| {
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
};
|
||||
|
||||
// nb: the query currently shows messages from blocked contacts in groups.
|
||||
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
||||
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
||||
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
||||
// shown at all permanent in the chatlist.
|
||||
|
||||
let mut ids = if let Some(query_contact_id) = query_contact_id {
|
||||
// show chats shared with a given contact
|
||||
context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
|
||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![query_contact_id as i32],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
||||
// show archived chats
|
||||
context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
|
||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
} else if let Some(query) = query {
|
||||
let query = query.trim().to_string();
|
||||
ensure!(!query.is_empty(), "missing query");
|
||||
|
||||
let str_like_cmd = format!("%{}%", query);
|
||||
context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.name LIKE ? \
|
||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![str_like_cmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
} else {
|
||||
// show normal chatlist
|
||||
let mut ids = context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c \
|
||||
LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.archived=0 \
|
||||
GROUP BY c.id \
|
||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?;
|
||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
||||
if last_deaddrop_fresh_msg_id > 0 {
|
||||
ids.push((1, last_deaddrop_fresh_msg_id));
|
||||
}
|
||||
add_archived_link_item = 1;
|
||||
}
|
||||
ids
|
||||
};
|
||||
|
||||
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
||||
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
|
||||
}
|
||||
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
|
||||
}
|
||||
|
||||
Ok(Chatlist { context, ids })
|
||||
}
|
||||
|
||||
/// Find out the number of chats.
|
||||
pub fn len(&self) -> usize {
|
||||
self.ids.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.ids.is_empty()
|
||||
}
|
||||
|
||||
/// Get a single chat ID of a chatlist.
|
||||
///
|
||||
/// To get the message object from the message ID, use dc_get_chat().
|
||||
pub fn get_chat_id(&self, index: usize) -> u32 {
|
||||
if index >= self.ids.len() {
|
||||
return 0;
|
||||
}
|
||||
self.ids[index].0
|
||||
}
|
||||
|
||||
/// Get a single message ID of a chatlist.
|
||||
///
|
||||
/// To get the message object from the message ID, use dc_get_msg().
|
||||
pub fn get_msg_id(&self, index: usize) -> u32 {
|
||||
if index >= self.ids.len() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self.ids[index].1
|
||||
}
|
||||
|
||||
/// Get a summary for a chatlist index.
|
||||
///
|
||||
/// The summary is returned by a dc_lot_t object with the following fields:
|
||||
///
|
||||
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
|
||||
/// The string may be colored by having a look at text1_meaning.
|
||||
/// If there is no such name or it should not be displayed, the element is NULL.
|
||||
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
|
||||
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
|
||||
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
|
||||
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
|
||||
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
|
||||
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
|
||||
// 0 if not applicable.
|
||||
pub fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
|
||||
// The summary is created by the chat, not by the last message.
|
||||
// This is because we may want to display drafts here or stuff as
|
||||
// "is typing".
|
||||
// Also, sth. as "No messages" would not work if the summary comes from a message.
|
||||
|
||||
let mut ret = Lot::new();
|
||||
if index >= self.ids.len() {
|
||||
ret.text2 = Some("ErrBadChatlistIndex".to_string());
|
||||
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();
|
||||
}
|
||||
|
||||
Some(lastmsg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
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());
|
||||
} else {
|
||||
ret.fill(
|
||||
&mut lastmsg.unwrap(),
|
||||
chat,
|
||||
lastcontact.as_ref(),
|
||||
self.context,
|
||||
);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
||||
// We have an index over the state-column, this should be sufficient as there are typically
|
||||
// only few fresh messages.
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE m.state=10 \
|
||||
AND m.hidden=0 \
|
||||
AND c.blocked=2 \
|
||||
ORDER BY m.timestamp DESC, m.id DESC;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -3,10 +3,11 @@ 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_stock::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::job::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::x::*;
|
||||
|
||||
/// The available configuration keys.
|
||||
#[derive(
|
||||
@@ -48,14 +49,11 @@ pub enum Config {
|
||||
ConfiguredMailUser,
|
||||
ConfiguredMailPw,
|
||||
ConfiguredMailPort,
|
||||
ConfiguredMailSecurity,
|
||||
ConfiguredSendServer,
|
||||
ConfiguredSendUser,
|
||||
ConfiguredSendPw,
|
||||
ConfiguredSendPort,
|
||||
ConfiguredServerFlags,
|
||||
ConfiguredSendSecurity,
|
||||
ConfiguredE2EEEnabled,
|
||||
Configured,
|
||||
// Deprecated
|
||||
#[strum(serialize = "sys.version")]
|
||||
@@ -72,9 +70,19 @@ impl Context {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
|
||||
rel_path.map(|p| {
|
||||
let v = unsafe {
|
||||
let n = to_cstring(p);
|
||||
let res = dc_get_abs_path(self, n);
|
||||
free(n as *mut libc::c_void);
|
||||
res
|
||||
};
|
||||
let r = to_string(v);
|
||||
unsafe { free(v as *mut _) };
|
||||
r
|
||||
})
|
||||
}
|
||||
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),
|
||||
@@ -86,7 +94,12 @@ impl Context {
|
||||
|
||||
// Default values
|
||||
match key {
|
||||
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
|
||||
Config::Selfstatus => {
|
||||
let s = unsafe { dc_stock_str(self, 13) };
|
||||
let res = to_string(s);
|
||||
unsafe { free(s as *mut _) };
|
||||
Some(res)
|
||||
}
|
||||
_ => key.get_str("default").map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -102,28 +115,30 @@ 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 => {
|
||||
let def = self.stock_str(StockMessage::StatusLine);
|
||||
let val = if value.is_none() || value.unwrap() == def {
|
||||
let def = unsafe { dc_stock_str(self, 13) };
|
||||
let val = if value.is_none() || value.unwrap() == as_str(def) {
|
||||
None
|
||||
} else {
|
||||
value
|
||||
};
|
||||
|
||||
self.sql.set_config(self, key, val)
|
||||
let ret = self.sql.set_config(self, key, val);
|
||||
unsafe { free(def as *mut libc::c_void) };
|
||||
ret
|
||||
}
|
||||
_ => self.sql.set_config(self, key, value),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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, ¶m.addr, ¶m.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(¶m.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;
|
||||
/*&¶m->mail_user ==NULL -- the user can enter a loginname which is used by autoconfig then */
|
||||
/*&¶m->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, ¶m);
|
||||
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, ¶m);
|
||||
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, ¶m);
|
||||
|
||||
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, ¶m);
|
||||
|
||||
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, ¶m,
|
||||
);
|
||||
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, ¶m,
|
||||
);
|
||||
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(¶m);
|
||||
info!(context, 0, "Trying: {}", r_0,);
|
||||
|
||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||
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(¶m);
|
||||
info!(context, 0, "Trying: {}", r_1,);
|
||||
|
||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||
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(¶m);
|
||||
info!(context, 0, "Trying: {}", r_2,);
|
||||
|
||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
||||
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, ¶m)
|
||||
{
|
||||
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(¶m);
|
||||
info!(context, 0, "Trying: {}", r_3,);
|
||||
|
||||
if !context
|
||||
.smtp
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.connect(context, ¶m)
|
||||
{
|
||||
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(¶m);
|
||||
info!(context, 0, "Trying: {}", r_4);
|
||||
|
||||
if !context
|
||||
.smtp
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.connect(context, ¶m)
|
||||
{
|
||||
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,
|
||||
¶m,
|
||||
&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, ¶m) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
383
src/constants.rs
383
src/constants.rs
@@ -1,140 +1,153 @@
|
||||
//! Constants
|
||||
#![allow(non_camel_case_types, dead_code)]
|
||||
|
||||
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: usize = 0;
|
||||
pub const DC_CHAT_TYPE_SINGLE: usize = 100;
|
||||
pub const DC_CHAT_TYPE_GROUP: usize = 120;
|
||||
pub const DC_CHAT_TYPE_VERIFIED_GROUP: usize = 130;
|
||||
|
||||
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: usize = 0;
|
||||
pub const DC_STATE_IN_FRESH: usize = 10;
|
||||
pub const DC_STATE_IN_NOTICED: usize = 13;
|
||||
pub const DC_STATE_IN_SEEN: usize = 16;
|
||||
pub const DC_STATE_OUT_PREPARING: usize = 18;
|
||||
pub const DC_STATE_OUT_DRAFT: usize = 19;
|
||||
pub const DC_STATE_OUT_PENDING: usize = 20;
|
||||
pub const DC_STATE_OUT_FAILED: usize = 24;
|
||||
/// to check if a mail was sent, use dc_msg_is_sent()
|
||||
pub const DC_STATE_OUT_DELIVERED: usize = 26;
|
||||
pub const DC_STATE_OUT_MDN_RCVD: usize = 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_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;
|
||||
|
||||
/// Text message.
|
||||
/// The text of the message is set using dc_msg_set_text()
|
||||
/// and retrieved with dc_msg_get_text().
|
||||
pub const DC_MSG_TEXT: usize = 10;
|
||||
|
||||
/// Image message.
|
||||
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
|
||||
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
|
||||
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
|
||||
pub const DC_MSG_IMAGE: usize = 20;
|
||||
|
||||
/// Animated GIF message.
|
||||
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||
pub const DC_MSG_GIF: usize = 21;
|
||||
|
||||
/// Message containing an Audio file.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||
pub const DC_MSG_AUDIO: usize = 40;
|
||||
|
||||
/// A voice message that was directly recorded by the user.
|
||||
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
|
||||
pub const DC_MSG_VOICE: usize = 41;
|
||||
|
||||
/// Video messages.
|
||||
/// File, width, height and durarion
|
||||
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
|
||||
/// and retrieved via
|
||||
/// dc_msg_get_file(), dc_msg_get_width(),
|
||||
/// dc_msg_get_height(), dc_msg_get_duration().
|
||||
pub const DC_MSG_VIDEO: usize = 50;
|
||||
|
||||
/// Message containing any file, eg. a PDF.
|
||||
/// The file is set via dc_msg_set_file()
|
||||
/// and retrieved via dc_msg_get_file().
|
||||
pub const DC_MSG_FILE: usize = 60;
|
||||
|
||||
// Flags for configuring IMAP and SMTP servers.
|
||||
// These flags are optional
|
||||
// and may be set together with the username, password etc.
|
||||
// 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 +155,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,76 +167,21 @@ 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)]
|
||||
#[repr(i32)]
|
||||
pub enum Viewtype {
|
||||
Unknown = 0,
|
||||
/// Text message.
|
||||
/// The text of the message is set using dc_msg_set_text()
|
||||
/// and retrieved with dc_msg_get_text().
|
||||
Text = 10,
|
||||
|
||||
/// Image message.
|
||||
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
|
||||
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
|
||||
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
|
||||
Image = 20,
|
||||
|
||||
/// Animated GIF message.
|
||||
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||
Gif = 21,
|
||||
|
||||
/// Message containing an Audio file.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||
Audio = 40,
|
||||
|
||||
/// A voice message that was directly recorded by the user.
|
||||
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
|
||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
|
||||
Voice = 41,
|
||||
|
||||
/// Video messages.
|
||||
/// File, width, height and durarion
|
||||
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
|
||||
/// and retrieved via
|
||||
/// dc_msg_get_file(), dc_msg_get_width(),
|
||||
/// dc_msg_get_height(), dc_msg_get_duration().
|
||||
Video = 50,
|
||||
|
||||
/// Message containing any file, eg. a PDF.
|
||||
/// The file is set via dc_msg_set_file()
|
||||
/// and retrieved via dc_msg_get_file().
|
||||
File = 60,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn derive_display_works_as_expected() {
|
||||
assert_eq!(format!("{}", Viewtype::Audio), "Audio");
|
||||
}
|
||||
}
|
||||
|
||||
// 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 +240,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 +358,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 +423,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;
|
||||
|
||||
@@ -533,12 +491,3 @@ pub enum KeyType {
|
||||
Public = 0,
|
||||
Private = 1,
|
||||
}
|
||||
|
||||
pub const DC_CMD_GROUPNAME_CHANGED: libc::c_int = 2;
|
||||
pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
|
||||
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
|
||||
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
|
||||
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
|
||||
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;
|
||||
|
||||
1081
src/contact.rs
1081
src/contact.rs
File diff suppressed because it is too large
Load Diff
270
src/context.rs
270
src/context.rs
@@ -1,48 +1,44 @@
|
||||
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_contact::*;
|
||||
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 perform_inbox_jobs_needed: Arc<RwLock<i32>>,
|
||||
pub probe_imap_network: Arc<RwLock<i32>>,
|
||||
pub sentbox_thread: Arc<RwLock<dc_jobthread_t>>,
|
||||
pub mvbox_thread: Arc<RwLock<dc_jobthread_t>>,
|
||||
pub smtp: Arc<Mutex<Smtp>>,
|
||||
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
|
||||
pub oauth2_critical: Arc<Mutex<()>>,
|
||||
pub cb: Option<dc_callback_t>,
|
||||
pub os_name: Option<String>,
|
||||
pub os_name: *mut libc::c_char,
|
||||
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
|
||||
pub bob: Arc<RwLock<BobStatus>>,
|
||||
pub last_smeared_timestamp: Arc<RwLock<i64>>,
|
||||
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 +52,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 +76,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
dc_close(&self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RunningState {
|
||||
fn default() -> Self {
|
||||
RunningState {
|
||||
@@ -99,31 +85,57 @@ 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)]
|
||||
pub struct SmtpState {
|
||||
pub idle: bool,
|
||||
pub suspended: bool,
|
||||
pub doing_jobs: bool,
|
||||
pub suspended: i32,
|
||||
pub doing_jobs: i32,
|
||||
pub perform_jobs_needed: i32,
|
||||
pub probe_network: bool,
|
||||
pub probe_network: i32,
|
||||
}
|
||||
|
||||
// location handling
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct _dc_location {
|
||||
pub location_id: uint32_t,
|
||||
pub latitude: libc::c_double,
|
||||
pub longitude: libc::c_double,
|
||||
pub accuracy: libc::c_double,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: uint32_t,
|
||||
pub msg_id: uint32_t,
|
||||
pub chat_id: uint32_t,
|
||||
pub marker: *mut libc::c_char,
|
||||
pub independent: uint32_t,
|
||||
}
|
||||
|
||||
// create/open/config/information
|
||||
pub fn dc_context_new(
|
||||
cb: Option<dc_callback_t>,
|
||||
userdata: *mut libc::c_void,
|
||||
os_name: Option<String>,
|
||||
os_name: *const libc::c_char,
|
||||
) -> Context {
|
||||
Context {
|
||||
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 +146,7 @@ pub fn dc_context_new(
|
||||
})),
|
||||
userdata,
|
||||
cb,
|
||||
os_name,
|
||||
os_name: unsafe { dc_strdup_keep_null(os_name) },
|
||||
running_state: Arc::new(RwLock::new(Default::default())),
|
||||
sql: Sql::new(),
|
||||
smtp: Arc::new(Mutex::new(Smtp::new())),
|
||||
@@ -143,7 +155,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 +165,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(
|
||||
@@ -163,9 +175,8 @@ pub fn dc_context_new(
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
probe_imap_network: Arc::new(RwLock::new(0)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +206,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,28 +234,33 @@ 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(
|
||||
dc_job_add(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
130i32,
|
||||
msg_id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
0 as *const libc::c_char,
|
||||
0i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
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>) {
|
||||
context.sql.set_config(context, key, value).ok();
|
||||
unsafe fn cb_set_config(context: &Context, key: *const libc::c_char, value: *const libc::c_char) {
|
||||
let v = if value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(as_str(value))
|
||||
};
|
||||
context.sql.set_config(context, as_str(key), v);
|
||||
}
|
||||
|
||||
/* *
|
||||
@@ -254,8 +270,24 @@ fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
|
||||
*
|
||||
* @private @memberof Context
|
||||
*/
|
||||
fn cb_get_config(context: &Context, key: &str) -> Option<String> {
|
||||
context.sql.get_config(context, key)
|
||||
unsafe fn cb_get_config(
|
||||
context: &Context,
|
||||
key: *const libc::c_char,
|
||||
def: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let res = context
|
||||
.sql
|
||||
.get_config(context, as_str(key))
|
||||
.unwrap_or_else(|| to_string(def));
|
||||
to_cstring(res)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_context_unref(context: &mut Context) {
|
||||
if 0 != dc_is_open(context) {
|
||||
dc_close(context);
|
||||
}
|
||||
|
||||
free(context.os_name as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_close(context: &Context) {
|
||||
@@ -281,42 +313,50 @@ 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 {
|
||||
context.userdata as *mut _
|
||||
}
|
||||
|
||||
pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool {
|
||||
let mut success = false;
|
||||
pub unsafe fn dc_open(
|
||||
context: &Context,
|
||||
dbfile: *const libc::c_char,
|
||||
blobdir: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
let mut success = 0;
|
||||
if 0 != dc_is_open(context) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
|
||||
let blobdir = blobdir.unwrap_or_default();
|
||||
if !blobdir.is_empty() {
|
||||
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
|
||||
*context.blobdir.write().unwrap() = dir;
|
||||
} else {
|
||||
let dir = dbfile.to_string() + "-blobs";
|
||||
dc_create_folder(context, &dir);
|
||||
*context.blobdir.write().unwrap() = dir.strdup();
|
||||
if !dbfile.is_null() {
|
||||
*context.dbfile.write().unwrap() = dc_strdup(dbfile);
|
||||
if !blobdir.is_null() && 0 != *blobdir.offset(0isize) as libc::c_int {
|
||||
let dir = dc_strdup(blobdir);
|
||||
dc_ensure_no_slash(dir);
|
||||
*context.blobdir.write().unwrap() = dir;
|
||||
} else {
|
||||
let dir = dc_mprintf(b"%s-blobs\x00" as *const u8 as *const libc::c_char, dbfile);
|
||||
dc_create_folder(context, dir);
|
||||
*context.blobdir.write().unwrap() = dir;
|
||||
}
|
||||
// Create/open sqlite database, this may already use the blobdir
|
||||
if context.sql.open(context, as_path(dbfile), 0) {
|
||||
success = 1i32
|
||||
}
|
||||
}
|
||||
// Create/open sqlite database, this may already use the blobdir
|
||||
let dbfile_path = std::path::Path::new(dbfile);
|
||||
if context.sql.open(context, dbfile_path, 0) {
|
||||
success = true
|
||||
}
|
||||
if !success {
|
||||
if 0 == success {
|
||||
dc_close(context);
|
||||
}
|
||||
success
|
||||
@@ -335,10 +375,10 @@ 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;
|
||||
let contacts = dc_get_real_contact_cnt(context) as usize;
|
||||
let is_configured = context
|
||||
.sql
|
||||
.get_config_int(context, "configured")
|
||||
@@ -437,7 +477,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 +486,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())
|
||||
@@ -474,14 +515,14 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
|
||||
fingerprint_str,
|
||||
);
|
||||
|
||||
res.strdup()
|
||||
to_cstring(res)
|
||||
}
|
||||
|
||||
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,10 +537,11 @@ 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 ret = unsafe { dc_array_new(128 as size_t) };
|
||||
|
||||
for row in rows {
|
||||
let id: u32 = row?;
|
||||
ret.push(id);
|
||||
let id = row?;
|
||||
unsafe { dc_array_add_id(ret, id) };
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
@@ -507,19 +549,18 @@ pub fn dc_get_fresh_msgs(context: &Context) -> Vec<u32> {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
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 +576,31 @@ 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 ret = unsafe { dc_array_new(100 as size_t) };
|
||||
|
||||
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);
|
||||
unsafe { dc_array_add_id(ret, id? as u32) };
|
||||
}
|
||||
Ok(ret)
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.is_ok();
|
||||
|
||||
if success {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if !ret.is_null() {
|
||||
unsafe { dc_array_unref(ret) };
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
|
||||
@@ -574,24 +625,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);
|
||||
}
|
||||
}
|
||||
|
||||
585
src/dc_array.rs
585
src/dc_array.rs
@@ -1,163 +1,492 @@
|
||||
use crate::location::Location;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
const DC_ARRAY_MAGIC: uint32_t = 0x000a11aa;
|
||||
|
||||
/* * the structure behind dc_array_t */
|
||||
#[derive(Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum dc_array_t {
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_array_t {
|
||||
pub magic: uint32_t,
|
||||
pub allocated: size_t,
|
||||
pub count: size_t,
|
||||
pub type_0: libc::c_int,
|
||||
pub array: *mut uintptr_t,
|
||||
}
|
||||
|
||||
impl dc_array_t {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
dc_array_t::Uint(Vec::with_capacity(capacity))
|
||||
/**
|
||||
* @class dc_array_t
|
||||
*
|
||||
* An object containing a simple array.
|
||||
* This object is used in several places where functions need to return an array.
|
||||
* The items of the array are typically IDs.
|
||||
* To free an array object, use dc_array_unref().
|
||||
*/
|
||||
pub unsafe fn dc_array_unref(mut array: *mut dc_array_t) {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return;
|
||||
}
|
||||
|
||||
/// Constructs a new, empty `dc_array_t` holding locations with specified `capacity`.
|
||||
pub fn new_locations(capacity: usize) -> Self {
|
||||
dc_array_t::Locations(Vec::with_capacity(capacity))
|
||||
if (*array).type_0 == 1i32 {
|
||||
dc_array_free_ptr(array);
|
||||
}
|
||||
free((*array).array as *mut libc::c_void);
|
||||
(*array).magic = 0i32 as uint32_t;
|
||||
free(array as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub fn add_id(&mut self, item: uint32_t) {
|
||||
if let Self::Uint(array) = self {
|
||||
array.push(item);
|
||||
} else {
|
||||
panic!("Attempt to add id to array of other type");
|
||||
pub unsafe fn dc_array_free_ptr(array: *mut dc_array_t) {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return;
|
||||
}
|
||||
let mut i: size_t = 0i32 as size_t;
|
||||
while i < (*array).count {
|
||||
if (*array).type_0 == 1i32 {
|
||||
free(
|
||||
(*(*(*array).array.offset(i as isize) as *mut _dc_location)).marker
|
||||
as *mut libc::c_void,
|
||||
);
|
||||
}
|
||||
free(*(*array).array.offset(i as isize) as *mut libc::c_void);
|
||||
*(*array).array.offset(i as isize) = 0i32 as uintptr_t;
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_add_uint(mut array: *mut dc_array_t, item: uintptr_t) {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return;
|
||||
}
|
||||
if (*array).count == (*array).allocated {
|
||||
let newsize = (*array).allocated.wrapping_mul(2).wrapping_add(10);
|
||||
(*array).array = realloc(
|
||||
(*array).array as *mut libc::c_void,
|
||||
(newsize).wrapping_mul(::std::mem::size_of::<uintptr_t>()),
|
||||
) as *mut uintptr_t;
|
||||
assert!(!(*array).array.is_null());
|
||||
(*array).allocated = newsize as size_t
|
||||
}
|
||||
*(*array).array.offset((*array).count as isize) = item;
|
||||
(*array).count = (*array).count.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_add_id(array: *mut dc_array_t, item: uint32_t) {
|
||||
dc_array_add_uint(array, item as uintptr_t);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void) {
|
||||
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() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return 0i32 as size_t;
|
||||
}
|
||||
(*array).count
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_uint(array: *const dc_array_t, index: size_t) -> uintptr_t {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC || index >= (*array).count {
|
||||
return 0i32 as uintptr_t;
|
||||
}
|
||||
*(*array).array.offset(index as isize)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC || index >= (*array).count {
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
if (*array).type_0 == 1i32 {
|
||||
return (*(*(*array).array.offset(index as isize) as *mut _dc_location)).location_id;
|
||||
}
|
||||
*(*array).array.offset(index as isize) as uint32_t
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_ptr(array: *const dc_array_t, index: size_t) -> *mut libc::c_void {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC || index >= (*array).count {
|
||||
return 0 as *mut libc::c_void;
|
||||
}
|
||||
*(*array).array.offset(index as isize) as *mut libc::c_void
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_latitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0i32 as libc::c_double;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).latitude
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_longitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0i32 as libc::c_double;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).longitude
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_accuracy(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0i32 as libc::c_double;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).accuracy
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_timestamp(array: *const dc_array_t, index: size_t) -> i64 {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).timestamp
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_chat_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).chat_id
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_contact_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).contact_id
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_msg_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).msg_id
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *mut libc::c_char {
|
||||
if array.is_null()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0 as *mut libc::c_char;
|
||||
}
|
||||
dc_strdup_keep_null((*(*(*array).array.offset(index as isize) as *mut _dc_location)).marker)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|| (*array).magic != DC_ARRAY_MAGIC
|
||||
|| index >= (*array).count
|
||||
|| (*array).type_0 != 1i32
|
||||
|| *(*array).array.offset(index as isize) == 0
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn add_location(&mut self, location: Location) {
|
||||
if let Self::Locations(array) = self {
|
||||
array.push(location)
|
||||
} else {
|
||||
panic!("Attempt to add a location to array of other type");
|
||||
}
|
||||
}
|
||||
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).independent as libc::c_int
|
||||
}
|
||||
|
||||
pub fn get_id(&self, index: usize) -> uint32_t {
|
||||
match self {
|
||||
Self::Locations(array) => array[index].location_id,
|
||||
Self::Uint(array) => array[index] as uint32_t,
|
||||
}
|
||||
pub unsafe fn dc_array_search_id(
|
||||
array: *const dc_array_t,
|
||||
needle: uint32_t,
|
||||
ret_index: *mut size_t,
|
||||
) -> bool {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn get_location(&self, index: usize) -> &Location {
|
||||
if let Self::Locations(array) = self {
|
||||
&array[index]
|
||||
} else {
|
||||
panic!("Not an array of locations")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::Locations(array) => array.is_empty(),
|
||||
Self::Uint(array) => array.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the array.
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Locations(array) => array.len(),
|
||||
Self::Uint(array) => array.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
match self {
|
||||
Self::Locations(array) => array.clear(),
|
||||
Self::Uint(array) => array.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_id(&self, needle: u32) -> Option<usize> {
|
||||
if let Self::Uint(array) = self {
|
||||
for (i, &u) in array.iter().enumerate() {
|
||||
if u == needle {
|
||||
return Some(i);
|
||||
}
|
||||
let data: *mut uintptr_t = (*array).array;
|
||||
let mut i: size_t = 0;
|
||||
let cnt: size_t = (*array).count;
|
||||
while i < cnt {
|
||||
if *data.offset(i as isize) == needle as size_t {
|
||||
if !ret_index.is_null() {
|
||||
*ret_index = i
|
||||
}
|
||||
None
|
||||
} else {
|
||||
panic!("Attempt to search for id in array of other type");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_ids(&mut self) {
|
||||
if let dc_array_t::Uint(v) = self {
|
||||
v.sort();
|
||||
} else {
|
||||
panic!("Attempt to sort array of something other than uints");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
return true;
|
||||
}
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl From<Vec<u32>> for dc_array_t {
|
||||
fn from(array: Vec<u32>) -> Self {
|
||||
dc_array_t::Uint(array)
|
||||
pub unsafe fn dc_array_get_raw(array: *const dc_array_t) -> *const uintptr_t {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return 0 as *const uintptr_t;
|
||||
}
|
||||
(*array).array
|
||||
}
|
||||
|
||||
impl From<Vec<Location>> for dc_array_t {
|
||||
fn from(array: Vec<Location>) -> Self {
|
||||
dc_array_t::Locations(array)
|
||||
pub unsafe fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
|
||||
dc_array_new_typed(0, initsize)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_new_typed(type_0: libc::c_int, initsize: size_t) -> *mut dc_array_t {
|
||||
let mut array: *mut dc_array_t;
|
||||
array = calloc(1, ::std::mem::size_of::<dc_array_t>()) as *mut dc_array_t;
|
||||
assert!(!array.is_null());
|
||||
|
||||
(*array).magic = DC_ARRAY_MAGIC;
|
||||
(*array).count = 0i32 as size_t;
|
||||
(*array).allocated = if initsize < 1 { 1 } else { initsize };
|
||||
(*array).type_0 = type_0;
|
||||
(*array).array = malloc(
|
||||
(*array)
|
||||
.allocated
|
||||
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
|
||||
) as *mut uintptr_t;
|
||||
if (*array).array.is_null() {
|
||||
exit(48i32);
|
||||
}
|
||||
array
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_empty(mut array: *mut dc_array_t) {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return;
|
||||
}
|
||||
(*array).count = 0i32 as size_t;
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_duplicate(array: *const dc_array_t) -> *mut dc_array_t {
|
||||
let mut ret: *mut dc_array_t;
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC {
|
||||
return 0 as *mut dc_array_t;
|
||||
}
|
||||
ret = dc_array_new((*array).allocated);
|
||||
(*ret).count = (*array).count;
|
||||
memcpy(
|
||||
(*ret).array as *mut libc::c_void,
|
||||
(*array).array as *const libc::c_void,
|
||||
(*array)
|
||||
.count
|
||||
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
|
||||
);
|
||||
ret
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_sort_ids(array: *mut dc_array_t) {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC || (*array).count <= 1 {
|
||||
return;
|
||||
}
|
||||
qsort(
|
||||
(*array).array as *mut libc::c_void,
|
||||
(*array).count,
|
||||
::std::mem::size_of::<uintptr_t>(),
|
||||
Some(cmp_intptr_t),
|
||||
);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cmp_intptr_t(p1: *const libc::c_void, p2: *const libc::c_void) -> libc::c_int {
|
||||
let v1: uintptr_t = *(p1 as *mut uintptr_t);
|
||||
let v2: uintptr_t = *(p2 as *mut uintptr_t);
|
||||
return if v1 < v2 {
|
||||
-1i32
|
||||
} else if v1 > v2 {
|
||||
1i32
|
||||
} else {
|
||||
0i32
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_sort_strings(array: *mut dc_array_t) {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC || (*array).count <= 1 {
|
||||
return;
|
||||
}
|
||||
qsort(
|
||||
(*array).array as *mut libc::c_void,
|
||||
(*array).count,
|
||||
::std::mem::size_of::<*mut libc::c_char>(),
|
||||
Some(cmp_strings_t),
|
||||
);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cmp_strings_t(
|
||||
p1: *const libc::c_void,
|
||||
p2: *const libc::c_void,
|
||||
) -> libc::c_int {
|
||||
let v1: *const libc::c_char = *(p1 as *mut *const libc::c_char);
|
||||
let v2: *const libc::c_char = *(p2 as *mut *const libc::c_char);
|
||||
|
||||
strcmp(v1, v2)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_array_get_string(
|
||||
array: *const dc_array_t,
|
||||
sep: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
if array.is_null() || (*array).magic != DC_ARRAY_MAGIC || sep.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
let cnt = (*array).count as usize;
|
||||
let slice = std::slice::from_raw_parts((*array).array, cnt);
|
||||
let sep = as_str(sep);
|
||||
|
||||
let res = slice
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(String::with_capacity(2 * cnt), |mut res, (i, n)| {
|
||||
if i == 0 {
|
||||
res += &n.to_string();
|
||||
} else {
|
||||
res += sep;
|
||||
res += &n.to_string();
|
||||
}
|
||||
res
|
||||
});
|
||||
to_cstring(res)
|
||||
}
|
||||
|
||||
/// return comma-separated value-string from integer array
|
||||
pub unsafe fn dc_arr_to_string(arr: *const uint32_t, cnt: libc::c_int) -> *mut libc::c_char {
|
||||
if arr.is_null() || cnt == 0 {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
|
||||
let slice = std::slice::from_raw_parts(arr, cnt as usize);
|
||||
let res = slice.iter().enumerate().fold(
|
||||
String::with_capacity(2 * cnt as usize),
|
||||
|mut res, (i, n)| {
|
||||
if i == 0 {
|
||||
res += &n.to_string();
|
||||
} else {
|
||||
res += ",";
|
||||
res += &n.to_string();
|
||||
}
|
||||
res
|
||||
},
|
||||
);
|
||||
to_cstring(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
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);
|
||||
|
||||
dc_array_sort_ids(arr);
|
||||
|
||||
assert_eq!(dc_array_get_id(arr, 0 as size_t), 0);
|
||||
assert_eq!(dc_array_get_id(arr, 1 as size_t), 7);
|
||||
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_empty(arr);
|
||||
|
||||
dc_array_add_ptr(
|
||||
arr,
|
||||
b"XX\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||
);
|
||||
dc_array_add_ptr(
|
||||
arr,
|
||||
b"item1\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||
);
|
||||
dc_array_add_ptr(
|
||||
arr,
|
||||
b"bbb\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||
);
|
||||
dc_array_add_ptr(
|
||||
arr,
|
||||
b"aaa\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||
);
|
||||
dc_array_sort_strings(arr);
|
||||
|
||||
let str = dc_array_get_ptr(arr, 0 as size_t) as *mut libc::c_char;
|
||||
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "XX");
|
||||
|
||||
let str = dc_array_get_ptr(arr, 1 as size_t) as *mut libc::c_char;
|
||||
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "aaa");
|
||||
|
||||
let str = dc_array_get_ptr(arr, 2 as size_t) as *mut libc::c_char;
|
||||
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "bbb");
|
||||
|
||||
let str = dc_array_get_ptr(arr, 3 as size_t) as *mut libc::c_char;
|
||||
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "item1");
|
||||
|
||||
dc_array_unref(arr);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
2305
src/dc_chat.rs
Normal file
2305
src/dc_chat.rs
Normal file
File diff suppressed because it is too large
Load Diff
389
src/dc_chatlist.rs
Normal file
389
src/dc_chatlist.rs
Normal file
@@ -0,0 +1,389 @@
|
||||
use crate::context::*;
|
||||
use crate::dc_array::*;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_contact::*;
|
||||
use crate::dc_lot::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_stock::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/* * the structure behind dc_chatlist_t */
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_chatlist_t<'a> {
|
||||
pub magic: uint32_t,
|
||||
pub context: &'a Context,
|
||||
pub cnt: size_t,
|
||||
pub chatNlastmsg_ids: *mut dc_array_t,
|
||||
}
|
||||
|
||||
// handle chatlists
|
||||
pub unsafe fn dc_get_chatlist<'a>(
|
||||
context: &'a Context,
|
||||
listflags: libc::c_int,
|
||||
query_str: *const libc::c_char,
|
||||
query_id: uint32_t,
|
||||
) -> *mut dc_chatlist_t<'a> {
|
||||
let obj = dc_chatlist_new(context);
|
||||
|
||||
if 0 != dc_chatlist_load_from_db(obj, listflags, query_str, query_id) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
dc_chatlist_unref(obj);
|
||||
return 0 as *mut dc_chatlist_t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class dc_chatlist_t
|
||||
*
|
||||
* An object representing a single chatlist in memory.
|
||||
* Chatlist objects contain chat IDs
|
||||
* and, if possible, message IDs belonging to them.
|
||||
* The chatlist object is not updated;
|
||||
* if you want an update, you have to recreate the object.
|
||||
*
|
||||
* For a **typical chat overview**,
|
||||
* the idea is to get the list of all chats via dc_get_chatlist()
|
||||
* without any listflags (see below)
|
||||
* and to implement a "virtual list" or so
|
||||
* (the count of chats is known by dc_chatlist_get_cnt()).
|
||||
*
|
||||
* Only for the items that are in view
|
||||
* (the list may have several hundreds chats),
|
||||
* the UI should call dc_chatlist_get_summary() then.
|
||||
* dc_chatlist_get_summary() provides all elements needed for painting the item.
|
||||
*
|
||||
* On a click of such an item,
|
||||
* the UI should change to the chat view
|
||||
* and get all messages from this view via dc_get_chat_msgs().
|
||||
* Again, a "virtual list" is created
|
||||
* (the count of messages is known)
|
||||
* and for each messages that is scrolled into view, dc_get_msg() is called then.
|
||||
*
|
||||
* Why no listflags?
|
||||
* Without listflags, dc_get_chatlist() adds the deaddrop
|
||||
* and the archive "link" automatically as needed.
|
||||
* The UI can just render these items differently then.
|
||||
* Although the deaddrop link is currently always the first entry
|
||||
* and only present on new messages,
|
||||
* there is the rough idea that it can be optionally always present
|
||||
* and sorted into the list by date.
|
||||
* Rendering the deaddrop in the described way
|
||||
* would not add extra work in the UI then.
|
||||
*/
|
||||
pub unsafe fn dc_chatlist_new(context: &Context) -> *mut dc_chatlist_t {
|
||||
let mut chatlist: *mut dc_chatlist_t;
|
||||
chatlist = calloc(1, ::std::mem::size_of::<dc_chatlist_t>()) as *mut dc_chatlist_t;
|
||||
assert!(!chatlist.is_null());
|
||||
|
||||
(*chatlist).magic = 0xc4a71157u32;
|
||||
(*chatlist).context = context;
|
||||
(*chatlist).chatNlastmsg_ids = dc_array_new(128i32 as size_t);
|
||||
assert!(!(*chatlist).chatNlastmsg_ids.is_null());
|
||||
chatlist
|
||||
}
|
||||
|
||||
pub unsafe fn dc_chatlist_unref(mut chatlist: *mut dc_chatlist_t) {
|
||||
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||
return;
|
||||
}
|
||||
dc_chatlist_empty(chatlist);
|
||||
dc_array_unref((*chatlist).chatNlastmsg_ids);
|
||||
(*chatlist).magic = 0i32 as uint32_t;
|
||||
free(chatlist as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_chatlist_empty(mut chatlist: *mut dc_chatlist_t) {
|
||||
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||
return;
|
||||
}
|
||||
(*chatlist).cnt = 0i32 as size_t;
|
||||
dc_array_empty((*chatlist).chatNlastmsg_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a chatlist from the database to the chatlist object.
|
||||
*
|
||||
* @private @memberof dc_chatlist_t
|
||||
*/
|
||||
// TODO should return bool /rtn
|
||||
unsafe fn dc_chatlist_load_from_db(
|
||||
mut chatlist: *mut dc_chatlist_t,
|
||||
listflags: libc::c_int,
|
||||
query__: *const libc::c_char,
|
||||
query_contact_id: u32,
|
||||
) -> libc::c_int {
|
||||
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||
return 0;
|
||||
}
|
||||
dc_chatlist_empty(chatlist);
|
||||
|
||||
let mut add_archived_link_item = 0;
|
||||
|
||||
// select with left join and minimum:
|
||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||
// which would refer the outer select and take a lot of time
|
||||
// - `GROUP BY` is needed several messages may have the same timestamp
|
||||
// - the list starts with the newest chats
|
||||
// nb: the query currently shows messages from blocked contacts in groups.
|
||||
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
||||
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
||||
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
||||
// shown at all permanent in the chatlist.
|
||||
|
||||
let process_row = |row: &rusqlite::Row| {
|
||||
let chat_id: i32 = row.get(0)?;
|
||||
// TODO: verify that it is okay for this to be Null
|
||||
let msg_id: i32 = row.get(1).unwrap_or_default();
|
||||
|
||||
Ok((chat_id, msg_id))
|
||||
};
|
||||
|
||||
let process_rows = |rows: rusqlite::MappedRows<_>| {
|
||||
for row in rows {
|
||||
let (id1, id2) = row?;
|
||||
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, id1 as u32);
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, id2 as u32);
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// nb: the query currently shows messages from blocked contacts in groups.
|
||||
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
||||
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
||||
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
||||
// shown at all permanent in the chatlist.
|
||||
|
||||
let success = if query_contact_id != 0 {
|
||||
// show chats shared with a given contact
|
||||
(*chatlist).context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
|
||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![query_contact_id as i32],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
} else if 0 != listflags & 0x1 {
|
||||
// show archived chats
|
||||
(*chatlist).context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
|
||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
} else if query__.is_null() {
|
||||
// show normal chatlist
|
||||
if 0 == listflags & 0x2 {
|
||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg((*chatlist).context);
|
||||
if last_deaddrop_fresh_msg_id > 0 {
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, 1);
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, last_deaddrop_fresh_msg_id);
|
||||
}
|
||||
add_archived_link_item = 1;
|
||||
}
|
||||
(*chatlist).context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c \
|
||||
LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.archived=0 \
|
||||
GROUP BY c.id \
|
||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
} else {
|
||||
let query = to_string(query__).trim().to_string();
|
||||
if query.is_empty() {
|
||||
return 1;
|
||||
} else {
|
||||
let strLikeCmd = format!("%{}%", query);
|
||||
(*chatlist).context.sql.query_map(
|
||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
||||
ON c.id=m.chat_id \
|
||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
||||
FROM msgs WHERE chat_id=c.id \
|
||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
||||
AND c.blocked=0 AND c.name LIKE ? \
|
||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
||||
params![strLikeCmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if 0 != add_archived_link_item && dc_get_archived_cnt((*chatlist).context) > 0 {
|
||||
if dc_array_get_cnt((*chatlist).chatNlastmsg_ids) == 0 && 0 != listflags & 0x4 {
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, 7);
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0);
|
||||
}
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, 6);
|
||||
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0);
|
||||
}
|
||||
(*chatlist).cnt = dc_array_get_cnt((*chatlist).chatNlastmsg_ids) / 2;
|
||||
|
||||
match success {
|
||||
Ok(_) => 1,
|
||||
Err(err) => {
|
||||
error!(
|
||||
(*chatlist).context,
|
||||
0, "chatlist: failed to load from database: {:?}", err
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context functions to work with chatlist
|
||||
pub fn dc_get_archived_cnt(context: &Context) -> libc::c_int {
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
||||
// we have an index over the state-column, this should be sufficient as there are typically only few fresh messages
|
||||
context
|
||||
.sql
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE m.state=10 \
|
||||
AND m.hidden=0 \
|
||||
AND c.blocked=2 \
|
||||
ORDER BY m.timestamp DESC, m.id DESC;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_chatlist_get_cnt(chatlist: *const dc_chatlist_t) -> size_t {
|
||||
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||
return 0i32 as size_t;
|
||||
}
|
||||
(*chatlist).cnt
|
||||
}
|
||||
|
||||
pub unsafe fn dc_chatlist_get_chat_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
|
||||
if chatlist.is_null()
|
||||
|| (*chatlist).magic != 0xc4a71157u32
|
||||
|| (*chatlist).chatNlastmsg_ids.is_null()
|
||||
|| index >= (*chatlist).cnt
|
||||
{
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2))
|
||||
}
|
||||
|
||||
pub unsafe fn dc_chatlist_get_msg_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
|
||||
if chatlist.is_null()
|
||||
|| (*chatlist).magic != 0xc4a71157u32
|
||||
|| (*chatlist).chatNlastmsg_ids.is_null()
|
||||
|| index >= (*chatlist).cnt
|
||||
{
|
||||
return 0i32 as uint32_t;
|
||||
}
|
||||
dc_array_get_id(
|
||||
(*chatlist).chatNlastmsg_ids,
|
||||
index.wrapping_mul(2).wrapping_add(1),
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_chatlist_get_summary<'a>(
|
||||
chatlist: *const dc_chatlist_t<'a>,
|
||||
index: size_t,
|
||||
mut chat: *mut Chat<'a>,
|
||||
) -> *mut dc_lot_t {
|
||||
let current_block: u64;
|
||||
/* The summary is created by the chat, not by the last message.
|
||||
This is because we may want to display drafts here or stuff as
|
||||
"is typing".
|
||||
Also, sth. as "No messages" would not work if the summary comes from a
|
||||
message. */
|
||||
/* the function never returns NULL */
|
||||
let mut ret: *mut dc_lot_t = dc_lot_new();
|
||||
let lastmsg_id: uint32_t;
|
||||
let mut lastmsg: *mut dc_msg_t = 0 as *mut dc_msg_t;
|
||||
let mut lastcontact: *mut dc_contact_t = 0 as *mut dc_contact_t;
|
||||
let mut chat_to_delete: *mut Chat = 0 as *mut Chat;
|
||||
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 || index >= (*chatlist).cnt {
|
||||
(*ret).text2 = dc_strdup(b"ErrBadChatlistIndex\x00" as *const u8 as *const libc::c_char)
|
||||
} else {
|
||||
lastmsg_id = dc_array_get_id(
|
||||
(*chatlist).chatNlastmsg_ids,
|
||||
index.wrapping_mul(2).wrapping_add(1),
|
||||
);
|
||||
if chat.is_null() {
|
||||
chat = dc_chat_new((*chatlist).context);
|
||||
chat_to_delete = chat;
|
||||
if !dc_chat_load_from_db(
|
||||
chat,
|
||||
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2)),
|
||||
) {
|
||||
(*ret).text2 =
|
||||
dc_strdup(b"ErrCannotReadChat\x00" as *const u8 as *const libc::c_char);
|
||||
current_block = 3777403817673069519;
|
||||
} else {
|
||||
current_block = 7651349459974463963;
|
||||
}
|
||||
} else {
|
||||
current_block = 7651349459974463963;
|
||||
}
|
||||
match current_block {
|
||||
3777403817673069519 => {}
|
||||
_ => {
|
||||
if 0 != lastmsg_id {
|
||||
lastmsg = dc_msg_new_untyped((*chatlist).context);
|
||||
dc_msg_load_from_db(lastmsg, (*chatlist).context, lastmsg_id);
|
||||
if (*lastmsg).from_id != 1i32 as libc::c_uint
|
||||
&& ((*chat).type_0 == 120i32 || (*chat).type_0 == 130i32)
|
||||
{
|
||||
lastcontact = dc_contact_new((*chatlist).context);
|
||||
dc_contact_load_from_db(
|
||||
lastcontact,
|
||||
&(*chatlist).context.sql,
|
||||
(*lastmsg).from_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (*chat).id == 6i32 as libc::c_uint {
|
||||
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
|
||||
} else if lastmsg.is_null() || (*lastmsg).from_id == 0i32 as libc::c_uint {
|
||||
(*ret).text2 = dc_stock_str((*chatlist).context, 1i32)
|
||||
} else {
|
||||
dc_lot_fill(ret, lastmsg, chat, lastcontact, (*chatlist).context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dc_msg_unref(lastmsg);
|
||||
dc_contact_unref(lastcontact);
|
||||
dc_chat_unref(chat_to_delete);
|
||||
ret
|
||||
}
|
||||
1531
src/dc_configure.rs
Normal file
1531
src/dc_configure.rs
Normal file
File diff suppressed because it is too large
Load Diff
1148
src/dc_contact.rs
Normal file
1148
src/dc_contact.rs
Normal file
File diff suppressed because it is too large
Load Diff
144
src/dc_dehtml.rs
144
src/dc_dehtml.rs
@@ -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();
|
||||
@@ -9,7 +11,7 @@ lazy_static! {
|
||||
struct Dehtml {
|
||||
strbuilder: String,
|
||||
add_text: AddText,
|
||||
last_href: Option<String>,
|
||||
last_href: *mut libc::c_char,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -21,88 +23,77 @@ 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,
|
||||
last_href: 0 as *mut libc::c_char,
|
||||
};
|
||||
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);
|
||||
free(dehtml.last_href as *mut libc::c_void);
|
||||
|
||||
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
|
||||
to_cstring(dehtml.strbuilder)
|
||||
}
|
||||
|
||||
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_string_lossy();
|
||||
|
||||
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;
|
||||
}
|
||||
"a" => {
|
||||
if let Some(ref last_href) = dehtml.last_href.take() {
|
||||
if !dehtml.last_href.is_null() {
|
||||
dehtml.strbuilder += "](";
|
||||
dehtml.strbuilder += last_href;
|
||||
dehtml.strbuilder += std::ffi::CStr::from_ptr((*dehtml).last_href)
|
||||
.to_string_lossy()
|
||||
.as_ref();
|
||||
dehtml.strbuilder += ")";
|
||||
free(dehtml.last_href as *mut libc::c_void);
|
||||
dehtml.last_href = 0 as *mut libc::c_char;
|
||||
}
|
||||
}
|
||||
"b" | "strong" => {
|
||||
@@ -115,14 +106,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 +131,13 @@ 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 += "[";
|
||||
}
|
||||
free(dehtml.last_href as *mut libc::c_void);
|
||||
dehtml.last_href = dc_strdup_keep_null(dc_attr_find(
|
||||
attr,
|
||||
b"href\x00" as *const u8 as *const libc::c_char,
|
||||
));
|
||||
if !dehtml.last_href.is_null() {
|
||||
dehtml.strbuilder += "[";
|
||||
}
|
||||
}
|
||||
"b" | "strong" => {
|
||||
|
||||
620
src/dc_e2ee.rs
620
src/dc_e2ee.rs
@@ -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::*;
|
||||
@@ -39,7 +34,6 @@ use crate::x::*;
|
||||
// to get the netto sizes, we subtract 1 mb header-overhead and the base64-overhead.
|
||||
// some defaults
|
||||
#[derive(Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_e2ee_helper_t {
|
||||
pub encryption_successfull: libc::c_int,
|
||||
pub cdata_to_free: *mut libc::c_void,
|
||||
@@ -60,7 +54,6 @@ impl Default for dc_e2ee_helper_t {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn dc_e2ee_encrypt(
|
||||
context: &Context,
|
||||
recipients_addr: *const clist,
|
||||
@@ -71,7 +64,7 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
mut in_out_message: *mut mailmime,
|
||||
helper: &mut dc_e2ee_helper_t,
|
||||
) {
|
||||
let mut ok_to_continue = true;
|
||||
let mut current_block: u64 = 0;
|
||||
let mut col: libc::c_int = 0i32;
|
||||
let mut do_encrypt: libc::c_int = 0i32;
|
||||
/*just a pointer into mailmime structure, must not be freed*/
|
||||
@@ -101,11 +94,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 +113,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 +126,7 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
iter1 = if !iter1.is_null() {
|
||||
(*iter1).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,19 +151,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,
|
||||
);
|
||||
@@ -195,12 +175,16 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
let p = peerstates[i as usize]
|
||||
.render_gossip_header(min_verified as usize);
|
||||
|
||||
if let Some(header) = p {
|
||||
if p.is_some() {
|
||||
let header = to_cstring(p.unwrap());
|
||||
mailimf_fields_add(
|
||||
imffields_encrypted,
|
||||
mailimf_field_new_custom(
|
||||
"Autocrypt-Gossip".strdup(),
|
||||
header.strdup(),
|
||||
strdup(
|
||||
b"Autocrypt-Gossip\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
),
|
||||
header,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -215,7 +199,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 +239,7 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,28 +250,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(
|
||||
@@ -301,7 +285,7 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
);
|
||||
mailmime_write_mem(plain, &mut col, message_to_encrypt);
|
||||
if (*plain).str_0.is_null() || (*plain).len <= 0 {
|
||||
ok_to_continue = false;
|
||||
current_block = 14181132614457621749;
|
||||
} else {
|
||||
if let Some(ctext_v) = dc_pgp_pk_encrypt(
|
||||
(*plain).str_0 as *const libc::c_void,
|
||||
@@ -310,12 +294,12 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
sign_key.as_ref(),
|
||||
) {
|
||||
let ctext_bytes = ctext_v.len();
|
||||
let ctext = ctext_v.strdup();
|
||||
helper.cdata_to_free = ctext as *mut _;
|
||||
let ctext = to_cstring(ctext_v);
|
||||
(*helper).cdata_to_free = ctext as *mut _;
|
||||
|
||||
/* 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,
|
||||
@@ -334,11 +318,11 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
as *mut libc::c_char,
|
||||
) as *mut libc::c_void,
|
||||
);
|
||||
static mut VERSION_CONTENT: [libc::c_char; 13] =
|
||||
static mut version_content: [libc::c_char; 13] =
|
||||
[86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0];
|
||||
let version_mime: *mut mailmime = new_data_part(
|
||||
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
|
||||
strlen(VERSION_CONTENT.as_mut_ptr()),
|
||||
version_content.as_mut_ptr() as *mut libc::c_void,
|
||||
strlen(version_content.as_mut_ptr()),
|
||||
b"application/pgp-encrypted\x00" as *const u8
|
||||
as *const libc::c_char
|
||||
as *mut libc::c_char,
|
||||
@@ -357,19 +341,27 @@ pub unsafe fn dc_e2ee_encrypt(
|
||||
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
|
||||
(*encrypted_part).mm_parent = in_out_message;
|
||||
mailmime_free(message_to_encrypt);
|
||||
helper.encryption_successfull = 1i32;
|
||||
(*helper).encryption_successfull = 1i32;
|
||||
current_block = 13824533195664196414;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_block = 13824533195664196414;
|
||||
}
|
||||
if ok_to_continue {
|
||||
let aheader = Aheader::new(addr, public_key, prefer_encrypt);
|
||||
mailimf_fields_add(
|
||||
imffields_unprotected,
|
||||
mailimf_field_new_custom(
|
||||
"Autocrypt".strdup(),
|
||||
aheader.to_string().strdup(),
|
||||
),
|
||||
);
|
||||
match current_block {
|
||||
14181132614457621749 => {}
|
||||
_ => {
|
||||
let aheader = Aheader::new(addr, public_key, prefer_encrypt);
|
||||
let rendered = to_cstring(aheader.to_string());
|
||||
|
||||
mailimf_fields_add(
|
||||
imffields_unprotected,
|
||||
mailimf_field_new_custom(
|
||||
strdup(b"Autocrypt\x00" as *const u8 as *const libc::c_char),
|
||||
rendered,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,7 +382,7 @@ unsafe fn new_data_part(
|
||||
default_content_type: *mut libc::c_char,
|
||||
default_encoding: libc::c_int,
|
||||
) -> *mut mailmime {
|
||||
let mut ok_to_continue = true;
|
||||
let mut current_block: u64;
|
||||
//char basename_buf[PATH_MAX];
|
||||
let mut encoding: *mut mailmime_mechanism;
|
||||
let content: *mut mailmime_content;
|
||||
@@ -401,7 +393,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
|
||||
@@ -410,7 +402,7 @@ unsafe fn new_data_part(
|
||||
}
|
||||
content = mailmime_content_new_with_str(content_type_str);
|
||||
if content.is_null() {
|
||||
ok_to_continue = false;
|
||||
current_block = 16266721588079097885;
|
||||
} else {
|
||||
do_encoding = 1i32;
|
||||
if (*(*content).ct_type).tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int {
|
||||
@@ -436,96 +428,119 @@ 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;
|
||||
}
|
||||
}
|
||||
if ok_to_continue {
|
||||
mime_fields = mailmime_fields_new_with_data(
|
||||
encoding,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
if mime_fields.is_null() {
|
||||
ok_to_continue = false;
|
||||
current_block = 16266721588079097885;
|
||||
} else {
|
||||
mime = mailmime_new_empty(content, mime_fields);
|
||||
if mime.is_null() {
|
||||
mailmime_fields_free(mime_fields);
|
||||
mailmime_content_free(content);
|
||||
current_block = 11057878835866523405;
|
||||
}
|
||||
} else {
|
||||
current_block = 11057878835866523405;
|
||||
}
|
||||
match current_block {
|
||||
16266721588079097885 => {}
|
||||
_ => {
|
||||
mime_fields = mailmime_fields_new_with_data(
|
||||
encoding,
|
||||
0 as *mut libc::c_char,
|
||||
0 as *mut libc::c_char,
|
||||
0 as *mut mailmime_disposition,
|
||||
0 as *mut mailmime_language,
|
||||
);
|
||||
if mime_fields.is_null() {
|
||||
current_block = 16266721588079097885;
|
||||
} else {
|
||||
if !data.is_null()
|
||||
&& data_bytes > 0
|
||||
&& (*mime).mm_type == MAILMIME_SINGLE as libc::c_int
|
||||
{
|
||||
mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes);
|
||||
mime = mailmime_new_empty(content, mime_fields);
|
||||
if mime.is_null() {
|
||||
mailmime_fields_free(mime_fields);
|
||||
mailmime_content_free(content);
|
||||
} else {
|
||||
if !data.is_null()
|
||||
&& data_bytes > 0
|
||||
&& (*mime).mm_type == MAILMIME_SINGLE as libc::c_int
|
||||
{
|
||||
mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes);
|
||||
}
|
||||
return mime;
|
||||
}
|
||||
return mime;
|
||||
current_block = 13668317689588454213;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ok_to_continue == false {
|
||||
if !encoding.is_null() {
|
||||
mailmime_mechanism_free(encoding);
|
||||
}
|
||||
if !content.is_null() {
|
||||
mailmime_content_free(content);
|
||||
match current_block {
|
||||
16266721588079097885 => {
|
||||
if !encoding.is_null() {
|
||||
mailmime_mechanism_free(encoding);
|
||||
}
|
||||
if !content.is_null() {
|
||||
mailmime_content_free(content);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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 +555,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 +594,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 +664,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 +716,7 @@ unsafe fn update_gossip_peerstates(
|
||||
cur1 = if !cur1.is_null() {
|
||||
(*cur1).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,13 +749,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 +764,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 +783,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 +795,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 +808,7 @@ unsafe fn decrypt_recursive(
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -822,16 +839,16 @@ unsafe fn decrypt_part(
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
ret_decrypted_mime: *mut *mut mailmime,
|
||||
) -> libc::c_int {
|
||||
let mut ok_to_continue = true;
|
||||
let current_block: u64;
|
||||
let mime_data: *mut mailmime_data;
|
||||
let mut mime_transfer_encoding: libc::c_int = MAILMIME_MECHANISM_BINARY as libc::c_int;
|
||||
/* mmap_string_unref()'d if set */
|
||||
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 +862,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 +874,7 @@ unsafe fn decrypt_part(
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
0 as *mut clistcell
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -870,7 +887,9 @@ unsafe fn decrypt_part(
|
||||
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
|
||||
if decoded_data.is_null() || decoded_data_bytes <= 0 {
|
||||
/* no error - but no data */
|
||||
ok_to_continue = false;
|
||||
current_block = 2554982661806928548;
|
||||
} else {
|
||||
current_block = 4488286894823169796;
|
||||
}
|
||||
} else {
|
||||
let r: libc::c_int;
|
||||
@@ -887,49 +906,53 @@ unsafe fn decrypt_part(
|
||||
|| transfer_decoding_buffer.is_null()
|
||||
|| decoded_data_bytes <= 0
|
||||
{
|
||||
ok_to_continue = false;
|
||||
current_block = 2554982661806928548;
|
||||
} else {
|
||||
decoded_data = transfer_decoding_buffer;
|
||||
current_block = 4488286894823169796;
|
||||
}
|
||||
}
|
||||
if ok_to_continue {
|
||||
/* encrypted, decoded data in decoded_data now ... */
|
||||
if !(0 == has_decrypted_pgp_armor(decoded_data, decoded_data_bytes as libc::c_int)) {
|
||||
let add_signatures = if ret_valid_signatures.is_empty() {
|
||||
Some(ret_valid_signatures)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
|
||||
if let Some(plain) = dc_pgp_pk_decrypt(
|
||||
decoded_data as *const libc::c_void,
|
||||
decoded_data_bytes,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
add_signatures,
|
||||
) {
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
|
||||
let mut index: size_t = 0i32 as size_t;
|
||||
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
|
||||
if mailmime_parse(
|
||||
plain_buf as *const _,
|
||||
plain_bytes,
|
||||
&mut index,
|
||||
&mut decrypted_mime,
|
||||
) != MAIL_NO_ERROR as libc::c_int
|
||||
|| decrypted_mime.is_null()
|
||||
{
|
||||
if !decrypted_mime.is_null() {
|
||||
mailmime_free(decrypted_mime);
|
||||
}
|
||||
match current_block {
|
||||
2554982661806928548 => {}
|
||||
_ => {
|
||||
/* encrypted, decoded data in decoded_data now ... */
|
||||
if !(0 == has_decrypted_pgp_armor(decoded_data, decoded_data_bytes as libc::c_int))
|
||||
{
|
||||
let add_signatures = if ret_valid_signatures.is_empty() {
|
||||
Some(ret_valid_signatures)
|
||||
} else {
|
||||
*ret_decrypted_mime = decrypted_mime;
|
||||
sth_decrypted = 1i32
|
||||
None
|
||||
};
|
||||
|
||||
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
|
||||
if let Some(plain) = dc_pgp_pk_decrypt(
|
||||
decoded_data as *const libc::c_void,
|
||||
decoded_data_bytes,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
add_signatures,
|
||||
) {
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
|
||||
let mut index: size_t = 0i32 as size_t;
|
||||
let mut decrypted_mime: *mut mailmime = 0 as *mut mailmime;
|
||||
if mailmime_parse(
|
||||
plain_buf as *const _,
|
||||
plain_bytes,
|
||||
&mut index,
|
||||
&mut decrypted_mime,
|
||||
) != MAIL_NO_ERROR as libc::c_int
|
||||
|| decrypted_mime.is_null()
|
||||
{
|
||||
if !decrypted_mime.is_null() {
|
||||
mailmime_free(decrypted_mime);
|
||||
}
|
||||
} else {
|
||||
*ret_decrypted_mime = decrypted_mime;
|
||||
sth_decrypted = 1i32
|
||||
}
|
||||
}
|
||||
std::mem::forget(plain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1011,7 +1034,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 +1042,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,153 +1057,28 @@ 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)
|
||||
}
|
||||
|
||||
#[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
|
||||
Chat-Group-ID: CovhGgau8M-
|
||||
Chat-Group-Name: Delta Chat Dev
|
||||
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
|
||||
=?utf-8?Q?all=3A?= rust core master ...
|
||||
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
sidenote for all: rust core master is broken currently ... so dont recomm=
|
||||
end to try to run with desktop or ios unless you are ready to hunt bugs
|
||||
|
||||
-- =20
|
||||
Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
|
||||
let mut index = 0;
|
||||
let mut decrypted_mime = std::ptr::null_mut();
|
||||
|
||||
let res = unsafe {
|
||||
mailmime_parse(
|
||||
plain_buf as *const _,
|
||||
plain_bytes,
|
||||
&mut index,
|
||||
&mut decrypted_mime,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
let msg1 = (*decrypted_mime).mm_data.mm_message.mm_msg_mime;
|
||||
let mut decoded_data = ptr::null();
|
||||
let mut decoded_data_bytes = 0;
|
||||
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
|
||||
|
||||
assert_eq!(
|
||||
mailmime_transfer_decode(
|
||||
msg1,
|
||||
&mut decoded_data,
|
||||
&mut decoded_data_bytes,
|
||||
&mut transfer_decoding_buffer,
|
||||
),
|
||||
1
|
||||
);
|
||||
println!(
|
||||
"{:?}",
|
||||
String::from_utf8_lossy(std::slice::from_raw_parts(
|
||||
decoded_data as *const u8,
|
||||
decoded_data_bytes as usize,
|
||||
))
|
||||
);
|
||||
|
||||
free(decoded_data as *mut _);
|
||||
}
|
||||
|
||||
assert_eq!(res, 0);
|
||||
assert!(!decrypted_mime.is_null());
|
||||
|
||||
unsafe { free(decrypted_mime as *mut _) };
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
/* 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
|
||||
}
|
||||
|
||||
847
src/dc_imex.rs
847
src/dc_imex.rs
File diff suppressed because it is too large
Load Diff
1272
src/dc_job.rs
Normal file
1272
src/dc_job.rs
Normal file
File diff suppressed because it is too large
Load Diff
209
src/dc_jobthread.rs
Normal file
209
src/dc_jobthread.rs
Normal 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;
|
||||
}
|
||||
750
src/dc_location.rs
Normal file
750
src/dc_location.rs
Normal file
@@ -0,0 +1,750 @@
|
||||
use crate::constants::Event;
|
||||
use crate::context::*;
|
||||
use crate::dc_array::*;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_job::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_param::*;
|
||||
use crate::dc_saxparser::*;
|
||||
use crate::dc_stock::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
// location handling
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_location_t {
|
||||
pub location_id: uint32_t,
|
||||
pub latitude: libc::c_double,
|
||||
pub longitude: libc::c_double,
|
||||
pub accuracy: libc::c_double,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: uint32_t,
|
||||
pub msg_id: uint32_t,
|
||||
pub chat_id: uint32_t,
|
||||
pub marker: *mut libc::c_char,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_kml_t {
|
||||
pub addr: *mut libc::c_char,
|
||||
pub locations: *mut dc_array_t,
|
||||
pub tag: libc::c_int,
|
||||
pub curr: dc_location_t,
|
||||
}
|
||||
|
||||
// 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 mut stock_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
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, 10i32);
|
||||
(*msg).text = dc_stock_system_msg(
|
||||
context,
|
||||
64,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0,
|
||||
);
|
||||
dc_param_set_int((*msg).param, 'S' as i32, 8i32);
|
||||
dc_send_msg(context, chat_id, msg);
|
||||
} else if 0 == seconds && is_sending_locations_before {
|
||||
stock_str = dc_stock_system_msg(
|
||||
context,
|
||||
65i32,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0i32 as uint32_t,
|
||||
);
|
||||
dc_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);
|
||||
dc_job_add(
|
||||
context,
|
||||
5007i32,
|
||||
chat_id as libc::c_int,
|
||||
0 as *const libc::c_char,
|
||||
seconds + 1i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(stock_str as *mut libc::c_void);
|
||||
dc_msg_unref(msg);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* job to send locations out to all chats that want them
|
||||
******************************************************************************/
|
||||
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, 0 as *const libc::c_char, 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| unsafe {
|
||||
let mut loc: *mut _dc_location =
|
||||
calloc(1, ::std::mem::size_of::<_dc_location>()) as *mut _dc_location;
|
||||
assert!(!loc.is_null(), "allocation failed");
|
||||
|
||||
(*loc).location_id = row.get(0)?;
|
||||
(*loc).latitude = row.get(1)?;
|
||||
(*loc).longitude = row.get(2)?;
|
||||
(*loc).accuracy = row.get(3)?;
|
||||
(*loc).timestamp = row.get(4)?;
|
||||
(*loc).independent = row.get(5)?;
|
||||
(*loc).msg_id = row.get(6)?;
|
||||
(*loc).contact_id = row.get(7)?;
|
||||
(*loc).chat_id = row.get(8)?;
|
||||
|
||||
if 0 != (*loc).msg_id {
|
||||
let txt: String = row.get(9)?;
|
||||
let txt_c = to_cstring(txt);
|
||||
if 0 != is_marker(txt_c) {
|
||||
(*loc).marker = txt_c;
|
||||
} else {
|
||||
free(txt_c as *mut _);
|
||||
}
|
||||
}
|
||||
Ok(loc)
|
||||
},
|
||||
|locations| {
|
||||
let ret = unsafe { dc_array_new_typed(1, 500) };
|
||||
|
||||
for location in locations {
|
||||
unsafe { dc_array_add_ptr(ret, location? as *mut libc::c_void) };
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|_| std::ptr::null_mut())
|
||||
}
|
||||
|
||||
// TODO should be bool /rtn
|
||||
unsafe fn is_marker(txt: *const libc::c_char) -> libc::c_int {
|
||||
if !txt.is_null() {
|
||||
let len: libc::c_int = dc_utf8_strlen(txt) as libc::c_int;
|
||||
if len == 1 && *txt.offset(0isize) as libc::c_int != ' ' as i32 {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
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 { to_cstring(ret) }
|
||||
} 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();
|
||||
to_cstring(res)
|
||||
}
|
||||
|
||||
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: *const dc_array_t,
|
||||
independent: libc::c_int,
|
||||
) -> u32 {
|
||||
if chat_id <= 9 || locations.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 i in 0..dc_array_get_cnt(locations) {
|
||||
let location = dc_array_get_ptr(locations, i as size_t) as *mut dc_location_t;
|
||||
|
||||
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,
|
||||
) -> *mut dc_kml_t {
|
||||
let mut kml: *mut dc_kml_t = calloc(1, ::std::mem::size_of::<dc_kml_t>()) as *mut dc_kml_t;
|
||||
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 = dc_array_new_typed(1, 100 as size_t);
|
||||
dc_saxparser_init(&mut saxparser, kml 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: *mut dc_location_t =
|
||||
calloc(1, ::std::mem::size_of::<dc_location_t>()) as *mut dc_location_t;
|
||||
*location = (*kml).curr;
|
||||
dc_array_add_ptr((*kml).locations, location as *mut libc::c_void);
|
||||
}
|
||||
(*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) {
|
||||
if kml.is_null() {
|
||||
return;
|
||||
}
|
||||
dc_array_unref((*kml).locations);
|
||||
free((*kml).addr as *mut libc::c_void);
|
||||
free(kml as *mut libc::c_void);
|
||||
}
|
||||
|
||||
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, 10);
|
||||
(*msg).hidden = 1;
|
||||
dc_param_set_int((*msg).param, 'S' as i32, 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
let mut stock_str = 0 as *mut libc::c_char;
|
||||
|
||||
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() {
|
||||
stock_str = dc_stock_system_msg(
|
||||
context,
|
||||
65,
|
||||
0 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
0,
|
||||
);
|
||||
dc_add_device_msg(context, chat_id, stock_str);
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as usize,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(stock_str as *mut libc::c_void);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use crate::context::Context;
|
||||
use crate::sql::Sql;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct dc_loginparam_t {
|
||||
pub addr: String,
|
||||
pub mail_server: String,
|
||||
@@ -92,39 +91,34 @@ pub fn dc_loginparam_write(
|
||||
let prefix = prefix.as_ref();
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.addr)).ok();
|
||||
sql.set_config(context, key, Some(&loginparam.addr));
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_server))
|
||||
.ok();
|
||||
sql.set_config(context, key, Some(&loginparam.mail_server));
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_config_int(context, key, loginparam.mail_port).ok();
|
||||
sql.set_config_int(context, key, loginparam.mail_port);
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_user))
|
||||
.ok();
|
||||
sql.set_config(context, key, Some(&loginparam.mail_user));
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.mail_pw)).ok();
|
||||
sql.set_config(context, key, Some(&loginparam.mail_pw));
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_server))
|
||||
.ok();
|
||||
sql.set_config(context, key, Some(&loginparam.send_server));
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_config_int(context, key, loginparam.send_port).ok();
|
||||
sql.set_config_int(context, key, loginparam.send_port);
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_user))
|
||||
.ok();
|
||||
sql.set_config(context, key, Some(&loginparam.send_user));
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_config(context, key, Some(&loginparam.send_pw)).ok();
|
||||
sql.set_config(context, key, Some(&loginparam.send_pw));
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_config_int(context, key, loginparam.server_flags)
|
||||
.ok();
|
||||
sql.set_config_int(context, key, loginparam.server_flags);
|
||||
}
|
||||
|
||||
fn unset_empty(s: &String) -> Cow<String> {
|
||||
|
||||
167
src/dc_lot.rs
Normal file
167
src/dc_lot.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use crate::context::Context;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_contact::*;
|
||||
use crate::dc_msg::*;
|
||||
use crate::dc_stock::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/* * 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 dc_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: *const dc_msg_t,
|
||||
chat: *const Chat,
|
||||
contact: *const dc_contact_t,
|
||||
context: &Context,
|
||||
) {
|
||||
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() {
|
||||
return;
|
||||
}
|
||||
if (*msg).state == 19i32 {
|
||||
(*lot).text1 = dc_stock_str(context, 3i32);
|
||||
(*lot).text1_meaning = 1i32
|
||||
} else if (*msg).from_id == 1i32 as libc::c_uint {
|
||||
if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) {
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32
|
||||
} else {
|
||||
(*lot).text1 = dc_stock_str(context, 2i32);
|
||||
(*lot).text1_meaning = 3i32
|
||||
}
|
||||
} else if chat.is_null() {
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32
|
||||
} else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 {
|
||||
if 0 != dc_msg_is_info(msg) || contact.is_null() {
|
||||
(*lot).text1 = 0 as *mut libc::c_char;
|
||||
(*lot).text1_meaning = 0i32
|
||||
} else {
|
||||
if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint {
|
||||
(*lot).text1 = dc_contact_get_display_name(contact)
|
||||
} else {
|
||||
(*lot).text1 = dc_contact_get_first_name(contact)
|
||||
}
|
||||
(*lot).text1_meaning = 2i32
|
||||
}
|
||||
}
|
||||
(*lot).text2 =
|
||||
dc_msg_get_summarytext_by_raw((*msg).type_0, (*msg).text, (*msg).param, 160i32, context);
|
||||
(*lot).timestamp = dc_msg_get_timestamp(msg);
|
||||
(*lot).state = (*msg).state;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1223
src/dc_mimeparser.rs
1223
src/dc_mimeparser.rs
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::Params;
|
||||
use crate::dc_job::*;
|
||||
use crate::dc_msg::*;
|
||||
|
||||
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
|
||||
if context
|
||||
@@ -18,27 +17,29 @@ 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,
|
||||
0 as *const libc::c_char,
|
||||
0,
|
||||
);
|
||||
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
|
||||
}
|
||||
|
||||
dc_msg_unref(msg);
|
||||
}
|
||||
|
||||
1525
src/dc_msg.rs
Normal file
1525
src/dc_msg.rs
Normal file
File diff suppressed because it is too large
Load Diff
437
src/dc_param.rs
Normal file
437
src/dc_param.rs
Normal file
@@ -0,0 +1,437 @@
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/// for msgs and jobs
|
||||
pub const DC_PARAM_FILE: char = 'f';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_WIDTH: char = 'w';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_HEIGHT: char = 'h';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_DURATION: char = 'd';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_MIMETYPE: char = 'm';
|
||||
/// for msgs: incoming: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||
pub const DC_PARAM_GUARANTEE_E2EE: char = 'c';
|
||||
/// for msgs: decrypted with validation errors or without mutual set, if neither 'c' nor 'e' are preset, the messages is only transport encrypted
|
||||
pub const DC_PARAM_ERRONEOUS_E2EE: char = 'e';
|
||||
/// for msgs: force unencrypted message, either DC_FP_ADD_AUTOCRYPT_HEADER (1), DC_FP_NO_AUTOCRYPT_HEADER (2) or 0
|
||||
pub const DC_PARAM_FORCE_PLAINTEXT: char = 'u';
|
||||
/// for msgs: an incoming message which requests a MDN (aka read receipt)
|
||||
pub const DC_PARAM_WANTS_MDN: char = 'r';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_FORWARDED: char = 'a';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_CMD: char = 'S';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_CMD_ARG: char = 'E';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_CMD_ARG2: char = 'F';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_CMD_ARG3: char = 'G';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_CMD_ARG4: char = 'H';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_ERROR: char = 'L';
|
||||
/// for msgs in PREPARING: space-separated list of message IDs of forwarded copies
|
||||
pub const DC_PARAM_PREP_FORWARDS: char = 'P';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_SET_LATITUDE: char = 'l';
|
||||
/// for msgs
|
||||
pub const DC_PARAM_SET_LONGITUDE: char = 'n';
|
||||
|
||||
/// for jobs
|
||||
pub const DC_PARAM_SERVER_FOLDER: char = 'Z';
|
||||
/// for jobs
|
||||
pub const DC_PARAM_SERVER_UID: char = 'z';
|
||||
/// for jobs
|
||||
pub const DC_PARAM_ALSO_MOVE: char = 'M';
|
||||
/// for jobs: space-separated list of message recipients
|
||||
pub const DC_PARAM_RECIPIENTS: char = 'R';
|
||||
/// for groups
|
||||
pub const DC_PARAM_UNPROMOTED: char = 'U';
|
||||
/// for groups and contacts
|
||||
pub const DC_PARAM_PROFILE_IMAGE: char = 'i';
|
||||
/// for chats
|
||||
pub const DC_PARAM_SELFTALK: char = 'K';
|
||||
|
||||
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||
pub const DC_FP_ADD_AUTOCRYPT_HEADER: u8 = 1;
|
||||
pub const DC_FP_NO_AUTOCRYPT_HEADER: u8 = 2;
|
||||
|
||||
/// An object for handling key=value parameter lists; for the key, currently only
|
||||
/// a single character is allowed.
|
||||
///
|
||||
/// The object is used eg. by Chat or dc_msg_t, for readable parameter names,
|
||||
/// these classes define some DC_PARAM_* constantats.
|
||||
///
|
||||
/// Only for library-internal use.
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct dc_param_t {
|
||||
pub packed: *mut libc::c_char,
|
||||
}
|
||||
|
||||
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||
/* user functions */
|
||||
pub unsafe fn dc_param_exists(param: *mut dc_param_t, key: libc::c_int) -> libc::c_int {
|
||||
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
if param.is_null() || key == 0i32 {
|
||||
return 0i32;
|
||||
}
|
||||
return if !find_param((*param).packed, key, &mut p2).is_null() {
|
||||
1i32
|
||||
} else {
|
||||
0i32
|
||||
};
|
||||
}
|
||||
|
||||
unsafe extern "C" fn find_param(
|
||||
haystack: *mut libc::c_char,
|
||||
key: libc::c_int,
|
||||
ret_p2: *mut *mut libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut p1: *mut libc::c_char;
|
||||
let mut p2: *mut libc::c_char;
|
||||
p1 = haystack;
|
||||
loop {
|
||||
if p1.is_null() || *p1 as libc::c_int == 0i32 {
|
||||
return 0 as *mut libc::c_char;
|
||||
} else {
|
||||
if *p1 as libc::c_int == key && *p1.offset(1isize) as libc::c_int == '=' as i32 {
|
||||
break;
|
||||
}
|
||||
p1 = strchr(p1, '\n' as i32);
|
||||
if !p1.is_null() {
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
}
|
||||
}
|
||||
p2 = strchr(p1, '\n' as i32);
|
||||
if p2.is_null() {
|
||||
p2 = &mut *p1.offset(strlen(p1) as isize) as *mut libc::c_char
|
||||
}
|
||||
*ret_p2 = p2;
|
||||
|
||||
p1
|
||||
}
|
||||
|
||||
/* the value may be an empty string, "def" is returned only if the value unset. The result must be free()'d in any case. */
|
||||
pub unsafe fn dc_param_get(
|
||||
param: *const dc_param_t,
|
||||
key: libc::c_int,
|
||||
def: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut p1: *mut libc::c_char;
|
||||
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let bak: libc::c_char;
|
||||
let ret: *mut libc::c_char;
|
||||
if param.is_null() || key == 0i32 {
|
||||
return if !def.is_null() {
|
||||
dc_strdup(def)
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
};
|
||||
}
|
||||
p1 = find_param((*param).packed, key, &mut p2);
|
||||
if p1.is_null() {
|
||||
return if !def.is_null() {
|
||||
dc_strdup(def)
|
||||
} else {
|
||||
0 as *mut libc::c_char
|
||||
};
|
||||
}
|
||||
p1 = p1.offset(2isize);
|
||||
bak = *p2;
|
||||
*p2 = 0i32 as libc::c_char;
|
||||
ret = dc_strdup(p1);
|
||||
dc_rtrim(ret);
|
||||
*p2 = bak;
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_get_int(
|
||||
param: *const dc_param_t,
|
||||
key: libc::c_int,
|
||||
def: int32_t,
|
||||
) -> int32_t {
|
||||
if param.is_null() || key == 0i32 {
|
||||
return def;
|
||||
}
|
||||
let s = dc_param_get(param, key, 0 as *const libc::c_char);
|
||||
if s.is_null() {
|
||||
return def;
|
||||
}
|
||||
let ret = as_str(s).parse().unwrap_or_default();
|
||||
free(s as *mut libc::c_void);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of a parameter.
|
||||
*
|
||||
* @memberof dc_param_t
|
||||
* @param param Parameter object to query.
|
||||
* @param key Key of the parameter to get, one of the DC_PARAM_* constants.
|
||||
* @param def Value to return if the parameter is not set.
|
||||
* @return The stored value or the default value.
|
||||
*/
|
||||
pub unsafe fn dc_param_get_float(
|
||||
param: *const dc_param_t,
|
||||
key: libc::c_int,
|
||||
def: libc::c_double,
|
||||
) -> libc::c_double {
|
||||
if param.is_null() || key == 0 {
|
||||
return def;
|
||||
}
|
||||
|
||||
let str = dc_param_get(param, key, std::ptr::null());
|
||||
if str.is_null() {
|
||||
return def;
|
||||
}
|
||||
|
||||
let ret = dc_atof(str) as libc::c_double;
|
||||
free(str as *mut libc::c_void);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_set(
|
||||
mut param: *mut dc_param_t,
|
||||
key: libc::c_int,
|
||||
value: *const libc::c_char,
|
||||
) {
|
||||
let mut old1: *mut libc::c_char;
|
||||
let mut old2: *mut libc::c_char;
|
||||
let new1: *mut libc::c_char;
|
||||
if param.is_null() || key == 0i32 {
|
||||
return;
|
||||
}
|
||||
old1 = (*param).packed;
|
||||
old2 = 0 as *mut libc::c_char;
|
||||
if !old1.is_null() {
|
||||
let p1: *mut libc::c_char;
|
||||
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
p1 = find_param(old1, key, &mut p2);
|
||||
if !p1.is_null() {
|
||||
*p1 = 0i32 as libc::c_char;
|
||||
old2 = p2
|
||||
} else if value.is_null() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
dc_rtrim(old1);
|
||||
dc_ltrim(old2);
|
||||
if !old1.is_null() && *old1.offset(0isize) as libc::c_int == 0i32 {
|
||||
old1 = 0 as *mut libc::c_char
|
||||
}
|
||||
if !old2.is_null() && *old2.offset(0isize) as libc::c_int == 0i32 {
|
||||
old2 = 0 as *mut libc::c_char
|
||||
}
|
||||
if !value.is_null() {
|
||||
new1 = dc_mprintf(
|
||||
b"%s%s%c=%s%s%s\x00" as *const u8 as *const libc::c_char,
|
||||
if !old1.is_null() {
|
||||
old1
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
if !old1.is_null() {
|
||||
b"\n\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
key,
|
||||
value,
|
||||
if !old2.is_null() {
|
||||
b"\n\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
if !old2.is_null() {
|
||||
old2
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
)
|
||||
} else {
|
||||
new1 = dc_mprintf(
|
||||
b"%s%s%s\x00" as *const u8 as *const libc::c_char,
|
||||
if !old1.is_null() {
|
||||
old1
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
if !old1.is_null() && !old2.is_null() {
|
||||
b"\n\x00" as *const u8 as *const libc::c_char
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
if !old2.is_null() {
|
||||
old2
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
)
|
||||
}
|
||||
free((*param).packed as *mut libc::c_void);
|
||||
(*param).packed = new1;
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_set_int(param: *mut dc_param_t, key: libc::c_int, value: int32_t) {
|
||||
if param.is_null() || key == 0i32 {
|
||||
return;
|
||||
}
|
||||
let value_str: *mut libc::c_char = dc_mprintf(
|
||||
b"%i\x00" as *const u8 as *const libc::c_char,
|
||||
value as libc::c_int,
|
||||
);
|
||||
if value_str.is_null() {
|
||||
return;
|
||||
}
|
||||
dc_param_set(param, key, value_str);
|
||||
free(value_str as *mut libc::c_void);
|
||||
}
|
||||
|
||||
/* library-private */
|
||||
pub unsafe fn dc_param_new() -> *mut dc_param_t {
|
||||
let mut param: *mut dc_param_t;
|
||||
param = calloc(1, ::std::mem::size_of::<dc_param_t>()) as *mut dc_param_t;
|
||||
assert!(!param.is_null());
|
||||
(*param).packed = calloc(1, 1) as *mut libc::c_char;
|
||||
|
||||
param
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_empty(param: *mut dc_param_t) {
|
||||
if param.is_null() {
|
||||
return;
|
||||
}
|
||||
*(*param).packed.offset(0isize) = 0i32 as libc::c_char;
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_unref(param: *mut dc_param_t) {
|
||||
if param.is_null() {
|
||||
return;
|
||||
}
|
||||
dc_param_empty(param);
|
||||
free((*param).packed as *mut libc::c_void);
|
||||
free(param as *mut libc::c_void);
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_set_packed(mut param: *mut dc_param_t, packed: *const libc::c_char) {
|
||||
if param.is_null() {
|
||||
return;
|
||||
}
|
||||
dc_param_empty(param);
|
||||
if !packed.is_null() {
|
||||
free((*param).packed as *mut libc::c_void);
|
||||
(*param).packed = dc_strdup(packed)
|
||||
};
|
||||
}
|
||||
|
||||
pub unsafe fn dc_param_set_urlencoded(mut param: *mut dc_param_t, urlencoded: *const libc::c_char) {
|
||||
if param.is_null() {
|
||||
return;
|
||||
}
|
||||
dc_param_empty(param);
|
||||
if !urlencoded.is_null() {
|
||||
free((*param).packed as *mut libc::c_void);
|
||||
(*param).packed = dc_strdup(urlencoded);
|
||||
dc_str_replace(
|
||||
&mut (*param).packed,
|
||||
b"&\x00" as *const u8 as *const libc::c_char,
|
||||
b"\n\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameter to a float.
|
||||
*
|
||||
* @memberof dc_param_t
|
||||
* @param param Parameter object to modify.
|
||||
* @param key Key of the parameter to modify, one of the DC_PARAM_* constants.
|
||||
* @param value Value to store for key.
|
||||
* @return None.
|
||||
*/
|
||||
pub unsafe fn dc_param_set_float(param: *mut dc_param_t, key: libc::c_int, value: libc::c_double) {
|
||||
if param.is_null() || key == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let value_str = dc_ftoa(value);
|
||||
if value_str.is_null() {
|
||||
return;
|
||||
}
|
||||
dc_param_set(param, key, value_str);
|
||||
free(value_str as *mut libc::c_void);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn test_dc_param() {
|
||||
unsafe {
|
||||
let p1: *mut dc_param_t = dc_param_new();
|
||||
dc_param_set_packed(
|
||||
p1,
|
||||
b"\r\n\r\na=1\nb=2\n\nc = 3 \x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
|
||||
assert_eq!(dc_param_get_int(p1, 'a' as i32, 0), 1);
|
||||
assert_eq!(dc_param_get_int(p1, 'b' as i32, 0), 2);
|
||||
assert_eq!(dc_param_get_int(p1, 'c' as i32, 0), 0);
|
||||
assert_eq!(dc_param_exists(p1, 'c' as i32), 0);
|
||||
|
||||
dc_param_set_int(p1, 'd' as i32, 4i32);
|
||||
|
||||
assert_eq!(dc_param_get_int(p1, 'd' as i32, 0), 4);
|
||||
|
||||
dc_param_empty(p1);
|
||||
dc_param_set(
|
||||
p1,
|
||||
'a' as i32,
|
||||
b"foo\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
dc_param_set_int(p1, 'b' as i32, 2i32);
|
||||
dc_param_set(p1, 'c' as i32, 0 as *const libc::c_char);
|
||||
dc_param_set_int(p1, 'd' as i32, 4i32);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr((*p1).packed as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"a=foo\nb=2\nd=4"
|
||||
);
|
||||
|
||||
dc_param_set(p1, 'b' as i32, 0 as *const libc::c_char);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr((*p1).packed as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"a=foo\nd=4",
|
||||
);
|
||||
|
||||
dc_param_set(p1, 'a' as i32, 0 as *const libc::c_char);
|
||||
dc_param_set(p1, 'd' as i32, 0 as *const libc::c_char);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr((*p1).packed as *const libc::c_char)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"",
|
||||
);
|
||||
|
||||
dc_param_unref(p1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
336
src/dc_qr.rs
Normal file
336
src/dc_qr.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
use crate::context::Context;
|
||||
use crate::dc_chat::*;
|
||||
use crate::dc_contact::*;
|
||||
use crate::dc_lot::*;
|
||||
use crate::dc_param::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::key::*;
|
||||
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: *mut dc_param_t = dc_param_new();
|
||||
dc_param_set_urlencoded(param, fragment);
|
||||
addr = dc_param_get(param, 'a' as i32, 0 as *const libc::c_char);
|
||||
if !addr.is_null() {
|
||||
let mut urlencoded: *mut libc::c_char =
|
||||
dc_param_get(param, 'n' as i32, 0 as *const libc::c_char);
|
||||
if !urlencoded.is_null() {
|
||||
name = dc_urldecode(urlencoded);
|
||||
dc_normalize_name(name);
|
||||
free(urlencoded as *mut libc::c_void);
|
||||
}
|
||||
invitenumber = dc_param_get(param, 'i' as i32, 0 as *const libc::c_char);
|
||||
auth = dc_param_get(param, 's' as i32, 0 as *const libc::c_char);
|
||||
grpid = dc_param_get(param, 'x' as i32, 0 as *const libc::c_char);
|
||||
if !grpid.is_null() {
|
||||
urlencoded = dc_param_get(param, 'g' as i32, 0 as *const libc::c_char);
|
||||
if !urlencoded.is_null() {
|
||||
grpname = dc_urldecode(urlencoded);
|
||||
free(urlencoded as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
dc_param_unref(param);
|
||||
}
|
||||
fingerprint = dc_normalize_fingerprint_c(payload);
|
||||
current_block = 5023038348526654800;
|
||||
} 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: *mut carray = dc_split_into_lines(qr);
|
||||
let mut i: libc::c_int = 0i32;
|
||||
while (i as libc::c_uint) < carray_count(lines) {
|
||||
let key: *mut libc::c_char =
|
||||
carray_get(lines, i as libc::c_uint) as *mut libc::c_char;
|
||||
dc_trim(key);
|
||||
let mut value: *mut libc::c_char = strchr(key, ':' as i32);
|
||||
if !value.is_null() {
|
||||
*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,
|
||||
);
|
||||
dc_normalize_name(name);
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
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 = dc_addr_normalize(addr);
|
||||
free(addr as *mut libc::c_void);
|
||||
addr = temp;
|
||||
if !dc_may_be_valid_addr(addr) {
|
||||
(*qr_parsed).state = 400i32;
|
||||
(*qr_parsed).text1 = dc_strdup(
|
||||
b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
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_ptr = if let Some(ref addr) = peerstate.addr {
|
||||
to_cstring(addr)
|
||||
} else {
|
||||
std::ptr::null()
|
||||
};
|
||||
(*qr_parsed).id = dc_add_or_lookup_contact(
|
||||
context,
|
||||
0 as *const libc::c_char,
|
||||
addr_ptr,
|
||||
0x80i32,
|
||||
0 as *mut libc::c_int,
|
||||
);
|
||||
free(addr_ptr as *mut _);
|
||||
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 = dc_add_or_lookup_contact(
|
||||
context,
|
||||
name,
|
||||
addr,
|
||||
0x80i32,
|
||||
0 as *mut libc::c_int,
|
||||
);
|
||||
(*qr_parsed).fingerprint = dc_strdup(fingerprint);
|
||||
(*qr_parsed).invitenumber = dc_strdup(invitenumber);
|
||||
(*qr_parsed).auth = dc_strdup(auth)
|
||||
}
|
||||
} else if !addr.is_null() {
|
||||
(*qr_parsed).state = 320i32;
|
||||
(*qr_parsed).id = dc_add_or_lookup_contact(
|
||||
context,
|
||||
name,
|
||||
addr,
|
||||
0x80i32,
|
||||
0 as *mut libc::c_int,
|
||||
)
|
||||
} 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
1095
src/dc_saxparser.rs
Normal file
1095
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
@@ -1,267 +1,375 @@
|
||||
use crate::dc_dehtml::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
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];
|
||||
pub unsafe fn dc_simplify_new() -> *mut dc_simplify_t {
|
||||
let simplify: *mut dc_simplify_t;
|
||||
simplify = calloc(1, ::std::mem::size_of::<dc_simplify_t>()) as *mut dc_simplify_t;
|
||||
assert!(!simplify.is_null());
|
||||
|
||||
// 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);
|
||||
simplify
|
||||
}
|
||||
|
||||
impl Simplify {
|
||||
pub fn new() -> Self {
|
||||
Simplify {
|
||||
is_forwarded: false,
|
||||
pub unsafe fn dc_simplify_unref(simplify: *mut dc_simplify_t) {
|
||||
if simplify.is_null() {
|
||||
return;
|
||||
}
|
||||
free(simplify as *mut libc::c_void);
|
||||
}
|
||||
|
||||
/* Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||
lineends etc.
|
||||
The data returned from Simplify() must be free()'d when no longer used, private */
|
||||
pub unsafe fn dc_simplify_simplify(
|
||||
mut simplify: *mut dc_simplify_t,
|
||||
in_unterminated: *const libc::c_char,
|
||||
in_bytes: libc::c_int,
|
||||
is_html: libc::c_int,
|
||||
is_msgrmsg: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
/* create a copy of the given buffer */
|
||||
let mut out: *mut libc::c_char;
|
||||
let mut temp: *mut libc::c_char;
|
||||
if simplify.is_null() || in_unterminated.is_null() || in_bytes <= 0i32 {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
(*simplify).is_forwarded = 0i32;
|
||||
(*simplify).is_cut_at_begin = 0i32;
|
||||
(*simplify).is_cut_at_end = 0i32;
|
||||
out = strndup(
|
||||
in_unterminated as *mut libc::c_char,
|
||||
in_bytes as libc::c_ulong,
|
||||
);
|
||||
if out.is_null() {
|
||||
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||
}
|
||||
if 0 != is_html {
|
||||
temp = dc_dehtml(out);
|
||||
if !temp.is_null() {
|
||||
free(out as *mut libc::c_void);
|
||||
out = temp
|
||||
}
|
||||
}
|
||||
dc_remove_cr_chars(out);
|
||||
temp = dc_simplify_simplify_plain_text(simplify, out, is_msgrmsg);
|
||||
if !temp.is_null() {
|
||||
free(out as *mut libc::c_void);
|
||||
out = temp
|
||||
}
|
||||
dc_remove_cr_chars(out);
|
||||
|
||||
/// Simplify 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)
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
unsafe fn dc_simplify_simplify_plain_text(
|
||||
mut simplify: *mut dc_simplify_t,
|
||||
buf_terminated: *const libc::c_char,
|
||||
is_msgrmsg: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
/* This function ...
|
||||
... removes all text after the line `-- ` (footer mark)
|
||||
... removes full quotes at the beginning and at the end of the text -
|
||||
these are all lines starting with the character `>`
|
||||
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
||||
/* split the given buffer into lines */
|
||||
let lines: *mut carray = dc_split_into_lines(buf_terminated);
|
||||
let mut l: libc::c_int;
|
||||
let mut l_first: libc::c_int = 0i32;
|
||||
/* if l_last is -1, there are no lines */
|
||||
let mut l_last: libc::c_int =
|
||||
carray_count(lines).wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
||||
let mut line: *mut libc::c_char;
|
||||
let mut footer_mark: libc::c_int = 0i32;
|
||||
l = l_first;
|
||||
while l <= l_last {
|
||||
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
footer_mark = 1i32
|
||||
}
|
||||
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
footer_mark = 1i32;
|
||||
(*simplify).is_cut_at_end = 1i32
|
||||
}
|
||||
if 0 != footer_mark {
|
||||
l_last = l - 1i32;
|
||||
/* done */
|
||||
break;
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
|
||||
out.retain(|c| c != '\r');
|
||||
out = self.simplify_plain_text(&out, is_msgrmsg);
|
||||
out.retain(|c| c != '\r');
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify Plain Text
|
||||
*/
|
||||
#[allow(non_snake_case)]
|
||||
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
||||
/* 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 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
|
||||
}
|
||||
l += 1
|
||||
}
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if line == "-----"
|
||||
|| line == "_____"
|
||||
|| line == "====="
|
||||
|| line == "*****"
|
||||
|| line == "~~~~~"
|
||||
{
|
||||
l_last = l;
|
||||
is_cut_at_end = true;
|
||||
/* done */
|
||||
}
|
||||
if l_last - l_first + 1i32 >= 3i32 {
|
||||
let line0: *mut libc::c_char =
|
||||
carray_get(lines, l_first as libc::c_uint) as *mut libc::c_char;
|
||||
let line1: *mut libc::c_char =
|
||||
carray_get(lines, (l_first + 1i32) as libc::c_uint) as *mut libc::c_char;
|
||||
let line2: *mut libc::c_char =
|
||||
carray_get(lines, (l_first + 2i32) as libc::c_uint) as *mut libc::c_char;
|
||||
if strcmp(
|
||||
line0,
|
||||
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
|
||||
&& *line2.offset(0isize) as libc::c_int == 0i32
|
||||
{
|
||||
(*simplify).is_forwarded = 1i32;
|
||||
l_first += 3i32
|
||||
}
|
||||
}
|
||||
l = l_first;
|
||||
while l <= l_last {
|
||||
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||
{
|
||||
l_last = l - 1i32;
|
||||
(*simplify).is_cut_at_end = 1i32;
|
||||
/* done */
|
||||
break;
|
||||
} else {
|
||||
l += 1
|
||||
}
|
||||
}
|
||||
if 0 == is_msgrmsg {
|
||||
let mut l_lastQuotedLine: libc::c_int = -1i32;
|
||||
l = l_last;
|
||||
while l >= l_first {
|
||||
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine = l
|
||||
} else if !is_empty_line(line) {
|
||||
break;
|
||||
}
|
||||
l -= 1
|
||||
}
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine = None;
|
||||
for l in (l_first..l_last).rev() {
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
if l_lastQuotedLine != -1i32 {
|
||||
l_last = l_lastQuotedLine - 1i32;
|
||||
(*simplify).is_cut_at_end = 1i32;
|
||||
if l_last > 0i32 {
|
||||
if is_empty_line(carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
if l_last > 0i32 {
|
||||
line = carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char;
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if 0 == is_msgrmsg {
|
||||
let mut l_lastQuotedLine_0: libc::c_int = -1i32;
|
||||
let mut hasQuotedHeadline: libc::c_int = 0i32;
|
||||
l = l_first;
|
||||
while l <= l_last {
|
||||
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine_0 = l
|
||||
} else if !is_empty_line(line) {
|
||||
if is_quoted_headline(line) && 0 == hasQuotedHeadline && l_lastQuotedLine_0 == -1i32
|
||||
{
|
||||
hasQuotedHeadline = 1i32
|
||||
} else {
|
||||
/* non-quoting line found */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
||||
l_last = last_quoted_line;
|
||||
is_cut_at_end = true;
|
||||
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];
|
||||
if is_quoted_headline(line) {
|
||||
l_last -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
l += 1
|
||||
}
|
||||
if !is_msgrmsg {
|
||||
let mut l_lastQuotedLine_0 = None;
|
||||
let mut hasQuotedHeadline = 0;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_plain_quote(line) {
|
||||
l_lastQuotedLine_0 = Some(l)
|
||||
} else if !is_empty_line(line) {
|
||||
if is_quoted_headline(line)
|
||||
&& 0 == hasQuotedHeadline
|
||||
&& l_lastQuotedLine_0.is_none()
|
||||
{
|
||||
hasQuotedHeadline = 1i32
|
||||
} else {
|
||||
/* non-quoting line found */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
||||
l_first = last_quoted_line + 1;
|
||||
is_cut_at_begin = true
|
||||
}
|
||||
if l_lastQuotedLine_0 != -1i32 {
|
||||
l_first = l_lastQuotedLine_0 + 1i32;
|
||||
(*simplify).is_cut_at_begin = 1i32
|
||||
}
|
||||
/* re-create buffer from the remaining lines */
|
||||
let mut ret = String::new();
|
||||
if 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];
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
if 0 != content_lines_added {
|
||||
if pending_linebreaks > 2i32 {
|
||||
pending_linebreaks = 2i32
|
||||
}
|
||||
while 0 != pending_linebreaks {
|
||||
ret += "\n";
|
||||
pending_linebreaks -= 1
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += line;
|
||||
content_lines_added += 1;
|
||||
pending_linebreaks = 1i32
|
||||
}
|
||||
}
|
||||
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
||||
ret += " [...]";
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
/* re-create buffer from the remaining lines */
|
||||
let mut ret = String::new();
|
||||
if 0 != (*simplify).is_cut_at_begin {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
||||
let mut content_lines_added: libc::c_int = 0i32;
|
||||
l = l_first;
|
||||
while l <= l_last {
|
||||
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||
if is_empty_line(line) {
|
||||
pending_linebreaks += 1
|
||||
} else {
|
||||
if 0 != content_lines_added {
|
||||
if pending_linebreaks > 2i32 {
|
||||
pending_linebreaks = 2i32
|
||||
}
|
||||
while 0 != pending_linebreaks {
|
||||
ret += "\n";
|
||||
pending_linebreaks -= 1
|
||||
}
|
||||
}
|
||||
// the incoming message might contain invalid UTF8
|
||||
ret += &to_string_lossy(line);
|
||||
content_lines_added += 1;
|
||||
pending_linebreaks = 1i32
|
||||
}
|
||||
l += 1
|
||||
}
|
||||
if 0 != (*simplify).is_cut_at_end
|
||||
&& (0 == (*simplify).is_cut_at_begin || 0 != content_lines_added)
|
||||
{
|
||||
ret += " [...]";
|
||||
}
|
||||
dc_free_splitted_lines(lines);
|
||||
|
||||
to_cstring(ret)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
dc_simplify_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);
|
||||
dc_simplify_unref(simplify);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||
let html: *const libc::c_char =
|
||||
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
dc_simplify_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);
|
||||
dc_simplify_unref(simplify);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||
let html: *const libc::c_char =
|
||||
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
dc_simplify_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);
|
||||
dc_simplify_unref(simplify);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_html_encoded() {
|
||||
let mut simplify = Simplify::new();
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
unsafe {
|
||||
let simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||
let html: *const libc::c_char =
|
||||
b"<>"'& äÄöÖüÜß fooÆçÇ ♦&noent;‎‏‌‍\x00"
|
||||
as *const u8 as *const libc::c_char;
|
||||
let plain: *mut libc::c_char =
|
||||
dc_simplify_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);
|
||||
dc_simplify_unref(simplify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
338
src/dc_stock.rs
Normal file
338
src/dc_stock.rs
Normal file
@@ -0,0 +1,338 @@
|
||||
use crate::constants::Event;
|
||||
use crate::context::Context;
|
||||
use crate::dc_contact::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/* Return the string with the given ID by calling DC_EVENT_GET_STRING.
|
||||
The result must be free()'d! */
|
||||
pub unsafe fn dc_stock_str(context: &Context, id: libc::c_int) -> *mut libc::c_char {
|
||||
return get_string(context, id, 0i32);
|
||||
}
|
||||
|
||||
unsafe fn get_string(context: &Context, id: libc::c_int, qty: libc::c_int) -> *mut libc::c_char {
|
||||
let mut ret: *mut libc::c_char;
|
||||
|
||||
ret =
|
||||
context.call_cb(Event::GET_STRING, id as uintptr_t, qty as uintptr_t) as *mut libc::c_char;
|
||||
|
||||
if ret.is_null() {
|
||||
ret = default_string(id)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/* Add translated strings that are used by the messager backend.
|
||||
As the logging functions may use these strings, do not log any
|
||||
errors from here. */
|
||||
unsafe fn default_string(id: libc::c_int) -> *mut libc::c_char {
|
||||
// TODO match on enum values /rtn
|
||||
match id {
|
||||
1 => {
|
||||
return dc_strdup(b"No messages.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
2 => {
|
||||
return dc_strdup(b"Me\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
3 => {
|
||||
return dc_strdup(b"Draft\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
4 => {
|
||||
return dc_strdup(b"%1$s member(s)\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
6 => {
|
||||
return dc_strdup(b"%1$s contact(s)\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
7 => {
|
||||
return dc_strdup(b"Voice message\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
8 => {
|
||||
return dc_strdup(b"Contact requests\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
9 => {
|
||||
return dc_strdup(b"Image\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
23 => {
|
||||
return dc_strdup(b"GIF\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
10 => {
|
||||
return dc_strdup(b"Video\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
11 => {
|
||||
return dc_strdup(b"Audio\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
12 => {
|
||||
return dc_strdup(b"File\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
66 => {
|
||||
return dc_strdup(b"Location\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
24 => {
|
||||
return dc_strdup(b"Encrypted message\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
13 => {
|
||||
return dc_strdup(b"Sent with my Delta Chat Messenger: https://delta.chat\x00"
|
||||
as *const u8 as *const libc::c_char)
|
||||
}
|
||||
14 => {
|
||||
return dc_strdup(b"Hello, I\'ve just created the group \"%1$s\" for us.\x00"
|
||||
as *const u8 as *const libc::c_char)
|
||||
}
|
||||
15 => {
|
||||
return dc_strdup(b"Group name changed from \"%1$s\" to \"%2$s\".\x00"
|
||||
as *const u8 as *const libc::c_char)
|
||||
}
|
||||
16 => {
|
||||
return dc_strdup(b"Group image changed.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
17 => {
|
||||
return dc_strdup(b"Member %1$s added.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
18 => {
|
||||
return dc_strdup(b"Member %1$s removed.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
19 => {
|
||||
return dc_strdup(b"Group left.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
64 => {
|
||||
return dc_strdup(b"Location streaming enabled.\x00" as *const u8
|
||||
as *const libc::c_char)
|
||||
}
|
||||
65 => {
|
||||
return dc_strdup(b"Location streaming disabled.\x00" as *const u8
|
||||
as *const libc::c_char)
|
||||
}
|
||||
62 => {
|
||||
return dc_strdup(b"%1$s by %2$s.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
63 => {
|
||||
return dc_strdup(b"%1$s by me.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
25 => {
|
||||
return dc_strdup(b"End-to-end encryption available.\x00" as
|
||||
*const u8 as *const libc::c_char)
|
||||
}
|
||||
27 => {
|
||||
return dc_strdup(b"Transport-encryption.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
28 => {
|
||||
return dc_strdup(b"No encryption.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
30 => {
|
||||
return dc_strdup(b"Fingerprints\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
31 => {
|
||||
return dc_strdup(b"Return receipt\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
32 => {
|
||||
return dc_strdup(b"This is a return receipt for the message \"%1$s\".\x00"
|
||||
as *const u8 as *const libc::c_char)
|
||||
}
|
||||
33 => {
|
||||
return dc_strdup(b"Group image deleted.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
34 => {
|
||||
return dc_strdup(b"End-to-end encryption preferred.\x00" as
|
||||
*const u8 as *const libc::c_char)
|
||||
}
|
||||
35 => {
|
||||
return dc_strdup(b"%1$s verified.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
36 => {
|
||||
return dc_strdup(b"Cannot verifiy %1$s\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
37 => {
|
||||
return dc_strdup(b"Changed setup for %1$s\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
40 => {
|
||||
return dc_strdup(b"Archived chats\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
41 => {
|
||||
return dc_strdup(b"Starred messages\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
42 => {
|
||||
return dc_strdup(b"Autocrypt Setup Message\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
43 => {
|
||||
return dc_strdup(b"This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.\x00"
|
||||
as *const u8 as *const libc::c_char)
|
||||
}
|
||||
50 => {
|
||||
return dc_strdup(b"Messages I sent to myself\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
29 => {
|
||||
return dc_strdup(b"This message was encrypted for another setup.\x00"
|
||||
as *const u8 as *const libc::c_char)
|
||||
}
|
||||
60 => {
|
||||
return dc_strdup(b"Cannot login as %1$s.\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
61 => {
|
||||
return dc_strdup(b"Response from %1$s: %2$s\x00" as *const u8 as
|
||||
*const libc::c_char)
|
||||
}
|
||||
_ => { }
|
||||
}
|
||||
|
||||
dc_strdup(b"ErrStr\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
|
||||
/* Replaces the first `%1$s` in the given String-ID by the given value.
|
||||
The result must be free()'d! */
|
||||
pub unsafe fn dc_stock_str_repl_string(
|
||||
context: &Context,
|
||||
id: libc::c_int,
|
||||
to_insert: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%1$s\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%1$d\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert,
|
||||
);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub unsafe fn dc_stock_str_repl_int(
|
||||
context: &Context,
|
||||
id: libc::c_int,
|
||||
to_insert_int: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
let mut ret: *mut libc::c_char = get_string(context, id, to_insert_int);
|
||||
let to_insert_str: *mut libc::c_char = dc_mprintf(
|
||||
b"%i\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert_int as libc::c_int,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%1$s\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert_str,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%1$d\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert_str,
|
||||
);
|
||||
free(to_insert_str as *mut libc::c_void);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/* Replaces the first `%1$s` and `%2$s` in the given String-ID by the two given strings.
|
||||
The result must be free()'d! */
|
||||
pub unsafe fn dc_stock_str_repl_string2(
|
||||
context: &Context,
|
||||
id: libc::c_int,
|
||||
to_insert: *const libc::c_char,
|
||||
to_insert2: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%1$s\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%1$d\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%2$s\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert2,
|
||||
);
|
||||
dc_str_replace(
|
||||
&mut ret,
|
||||
b"%2$d\x00" as *const u8 as *const libc::c_char,
|
||||
to_insert2,
|
||||
);
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/* Misc. */
|
||||
pub unsafe fn dc_stock_system_msg(
|
||||
context: &Context,
|
||||
str_id: libc::c_int,
|
||||
mut param1: *const libc::c_char,
|
||||
param2: *const libc::c_char,
|
||||
from_id: uint32_t,
|
||||
) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char;
|
||||
let mut mod_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
|
||||
let mut mod_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut from_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
|
||||
let mut from_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
if str_id == 17i32 || str_id == 18i32 {
|
||||
let mod_contact_id: uint32_t = dc_lookup_contact_id_by_addr(context, param1);
|
||||
if mod_contact_id != 0i32 as libc::c_uint {
|
||||
mod_contact = dc_get_contact(context, mod_contact_id);
|
||||
mod_displayname = dc_contact_get_name_n_addr(mod_contact);
|
||||
param1 = mod_displayname
|
||||
}
|
||||
}
|
||||
let action: *mut libc::c_char = dc_stock_str_repl_string2(context, str_id, param1, param2);
|
||||
if 0 != from_id {
|
||||
if 0 != strlen(action)
|
||||
&& *action.offset(strlen(action).wrapping_sub(1) as isize) as libc::c_int == '.' as i32
|
||||
{
|
||||
*action.offset(strlen(action).wrapping_sub(1) as isize) = 0i32 as libc::c_char
|
||||
}
|
||||
from_contact = dc_get_contact(context, from_id);
|
||||
from_displayname = dc_contact_get_display_name(from_contact);
|
||||
ret = dc_stock_str_repl_string2(
|
||||
context,
|
||||
if from_id == 1i32 as libc::c_uint {
|
||||
63i32
|
||||
} else {
|
||||
62i32
|
||||
},
|
||||
action,
|
||||
from_displayname,
|
||||
)
|
||||
} else {
|
||||
ret = dc_strdup(action)
|
||||
}
|
||||
free(action as *mut libc::c_void);
|
||||
free(from_displayname as *mut libc::c_void);
|
||||
free(mod_displayname as *mut libc::c_void);
|
||||
dc_contact_unref(from_contact);
|
||||
dc_contact_unref(mod_contact);
|
||||
|
||||
ret
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ptr;
|
||||
use std::ffi::CStr;
|
||||
|
||||
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,15 +18,57 @@ 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
|
||||
******************************************************************************/
|
||||
unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char {
|
||||
static mut HEX: [libc::c_char; 17] = [
|
||||
static mut hex: [libc::c_char; 17] = [
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0,
|
||||
];
|
||||
|
||||
HEX[(code as libc::c_int & 15i32) as usize]
|
||||
hex[(code as libc::c_int & 15i32) as usize]
|
||||
}
|
||||
|
||||
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
|
||||
@@ -79,90 +120,97 @@ fn hex_2_int(ch: libc::c_char) -> libc::c_char {
|
||||
}
|
||||
|
||||
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
|
||||
let mut ok_to_continue = true;
|
||||
let mut ret_str: *mut libc::c_char = ptr::null_mut();
|
||||
let mut current_block: u64;
|
||||
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut cur: *const libc::c_char = to_encode;
|
||||
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
if to_encode.is_null() || mmapstr.is_null() {
|
||||
ok_to_continue = false;
|
||||
current_block = 8550051112593613029;
|
||||
} else {
|
||||
current_block = 4644295000439058019;
|
||||
}
|
||||
loop {
|
||||
if !ok_to_continue {
|
||||
if !mmapstr.is_null() {
|
||||
mmap_string_free(mmapstr);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
||||
let begin: *const libc::c_char;
|
||||
let mut end: *const libc::c_char;
|
||||
let mut do_quote: bool;
|
||||
let mut quote_words: libc::c_int;
|
||||
begin = cur;
|
||||
end = begin;
|
||||
quote_words = 0i32;
|
||||
do_quote = true;
|
||||
while *cur as libc::c_int != '\u{0}' as i32 {
|
||||
get_word(cur, &mut cur, &mut do_quote);
|
||||
if !do_quote {
|
||||
break;
|
||||
}
|
||||
quote_words = 1i32;
|
||||
end = cur;
|
||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
||||
cur = cur.offset(1isize)
|
||||
}
|
||||
match current_block {
|
||||
8550051112593613029 => {
|
||||
if !mmapstr.is_null() {
|
||||
mmap_string_free(mmapstr);
|
||||
}
|
||||
if 0 != quote_words {
|
||||
if !quote_word(
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
||||
let begin: *const libc::c_char;
|
||||
let mut end: *const libc::c_char;
|
||||
let mut do_quote: bool;
|
||||
let mut quote_words: libc::c_int;
|
||||
begin = cur;
|
||||
end = begin;
|
||||
quote_words = 0i32;
|
||||
do_quote = true;
|
||||
while *cur as libc::c_int != '\u{0}' as i32 {
|
||||
get_word(cur, &mut cur, &mut do_quote);
|
||||
if !do_quote {
|
||||
break;
|
||||
}
|
||||
quote_words = 1i32;
|
||||
end = cur;
|
||||
if *cur as libc::c_int != '\u{0}' as i32 {
|
||||
cur = cur.offset(1isize)
|
||||
}
|
||||
}
|
||||
if 0 != quote_words {
|
||||
if !quote_word(
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
mmapstr,
|
||||
begin,
|
||||
end.wrapping_offset_from(begin) as size_t,
|
||||
) {
|
||||
current_block = 8550051112593613029;
|
||||
continue;
|
||||
}
|
||||
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
|
||||
if mmap_string_append_c(mmapstr, *end).is_null() {
|
||||
current_block = 8550051112593613029;
|
||||
continue;
|
||||
}
|
||||
end = end.offset(1isize)
|
||||
}
|
||||
if *end as libc::c_int != '\u{0}' as i32 {
|
||||
if mmap_string_append_len(
|
||||
mmapstr,
|
||||
end,
|
||||
cur.wrapping_offset_from(end) as size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
current_block = 8550051112593613029;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if mmap_string_append_len(
|
||||
mmapstr,
|
||||
begin,
|
||||
end.wrapping_offset_from(begin) as size_t,
|
||||
) {
|
||||
ok_to_continue = false;
|
||||
cur.wrapping_offset_from(begin) as size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
current_block = 8550051112593613029;
|
||||
continue;
|
||||
}
|
||||
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
|
||||
if mmap_string_append_c(mmapstr, *end).is_null() {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
end = end.offset(1isize)
|
||||
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
|
||||
current_block = 4644295000439058019;
|
||||
continue;
|
||||
}
|
||||
if *end as libc::c_int != '\u{0}' as i32 {
|
||||
if mmap_string_append_len(
|
||||
mmapstr,
|
||||
end,
|
||||
cur.wrapping_offset_from(end) as size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
if mmap_string_append_c(mmapstr, *cur).is_null() {
|
||||
current_block = 8550051112593613029;
|
||||
continue;
|
||||
}
|
||||
} else if mmap_string_append_len(
|
||||
mmapstr,
|
||||
begin,
|
||||
cur.wrapping_offset_from(begin) as size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
cur = cur.offset(1isize);
|
||||
current_block = 4644295000439058019;
|
||||
} else {
|
||||
ret_str = strdup((*mmapstr).str_0);
|
||||
current_block = 8550051112593613029;
|
||||
}
|
||||
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
|
||||
continue;
|
||||
}
|
||||
if mmap_string_append_c(mmapstr, *cur).is_null() {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
}
|
||||
cur = cur.offset(1isize);
|
||||
} else {
|
||||
ret_str = strdup((*mmapstr).str_0);
|
||||
ok_to_continue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,9 +319,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 +338,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 {
|
||||
@@ -331,7 +378,7 @@ unsafe fn dc_encode_modified_utf7(
|
||||
if 0 != bitstogo {
|
||||
let fresh8 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh8 = BASE64CHARS
|
||||
*fresh8 = base64chars
|
||||
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
|
||||
}
|
||||
let fresh9 = dst;
|
||||
@@ -402,7 +449,7 @@ unsafe fn dc_encode_modified_utf7(
|
||||
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
|
||||
let fresh14 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
|
||||
*fresh14 = base64chars[(if 0 != bitstogo {
|
||||
bitbuf >> bitstogo
|
||||
} else {
|
||||
bitbuf
|
||||
@@ -418,7 +465,7 @@ unsafe fn dc_encode_modified_utf7(
|
||||
if 0 != bitstogo {
|
||||
let fresh15 = dst;
|
||||
dst = dst.offset(1);
|
||||
*fresh15 = BASE64CHARS
|
||||
*fresh15 = base64chars
|
||||
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
|
||||
}
|
||||
let fresh16 = dst;
|
||||
@@ -435,15 +482,13 @@ unsafe fn dc_encode_modified_utf7(
|
||||
******************************************************************************/
|
||||
|
||||
// UTF7 modified base64 alphabet
|
||||
#[cfg(test)]
|
||||
static mut BASE64CHARS: [libc::c_char; 65] = [
|
||||
static mut base64chars: [libc::c_char; 65] = [
|
||||
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
|
||||
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
|
||||
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
|
||||
];
|
||||
|
||||
#[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 {
|
||||
@@ -472,7 +517,7 @@ unsafe fn dc_decode_modified_utf7(
|
||||
);
|
||||
i = 0i32 as libc::c_uint;
|
||||
while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong {
|
||||
base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
|
||||
base64[base64chars[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
|
||||
i = i.wrapping_add(1)
|
||||
}
|
||||
while *src as libc::c_int != '\u{0}' as i32 {
|
||||
@@ -617,8 +662,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 +698,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) {
|
||||
@@ -665,14 +710,14 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
|
||||
assert!(!cur.is_null());
|
||||
|
||||
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
|
||||
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
|
||||
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
|
||||
let raw = to_cstring(format!("={}", &hex::encode_upper(bytes)[..2]));
|
||||
libc::memcpy(target as *mut _, raw as *const _, 4);
|
||||
free(raw as *mut libc::c_void);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
@@ -852,15 +897,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);
|
||||
|
||||
@@ -3,8 +3,7 @@ use crate::dc_tools::*;
|
||||
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;
|
||||
|
||||
@@ -42,7 +41,7 @@ pub fn dc_token_lookup(
|
||||
params![namespc as i32, foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
.map(|s| unsafe { s.strdup() })
|
||||
.map(|s| unsafe { to_cstring(s) })
|
||||
.unwrap_or_else(|| std::ptr::null_mut())
|
||||
}
|
||||
|
||||
|
||||
1525
src/dc_tools.rs
1525
src/dc_tools.rs
File diff suppressed because it is too large
Load Diff
82
src/error.rs
82
src/error.rs
@@ -16,12 +16,6 @@ pub enum Error {
|
||||
SqlFailedToOpen,
|
||||
#[fail(display = "{:?}", _0)]
|
||||
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>;
|
||||
@@ -49,79 +43,3 @@ impl From<std::io::Error> for Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
return Err($crate::error::Error::Message($e.to_string()));
|
||||
};
|
||||
($fmt:expr, $($arg:tt)+) => {
|
||||
return Err($crate::error::Error::Message(format!($fmt, $($arg)+)));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! format_err {
|
||||
($e:expr) => {
|
||||
$crate::error::Error::Message($e.to_string());
|
||||
};
|
||||
($fmt:expr, $($arg:tt)+) => {
|
||||
$crate::error::Error::Message(format!($fmt, $($arg)+));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $e:expr) => {
|
||||
if !($cond) {
|
||||
bail!($e);
|
||||
}
|
||||
};
|
||||
($cond:expr, $fmt:expr, $($arg:tt)+) => {
|
||||
if !($cond) {
|
||||
bail!($fmt, $($arg)+);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_eq {
|
||||
($left:expr, $right:expr) => ({
|
||||
match (&$left, &$right) {
|
||||
(left_val, right_val) => {
|
||||
if !(*left_val == *right_val) {
|
||||
bail!(r#"assertion failed: `(left == right)`
|
||||
left: `{:?}`,
|
||||
right: `{:?}`"#, left_val, right_val)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
($left:expr, $right:expr,) => ({
|
||||
ensure_eq!($left, $right)
|
||||
});
|
||||
($left:expr, $right:expr, $($arg:tt)+) => ({
|
||||
match (&($left), &($right)) {
|
||||
(left_val, right_val) => {
|
||||
if !(*left_val == *right_val) {
|
||||
bail!(r#"assertion failed: `(left == right)`
|
||||
left: `{:?}`,
|
||||
right: `{:?}`: {}"#, left_val, right_val,
|
||||
format_args!($($arg)+))
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
209
src/imap.rs
209
src/imap.rs
@@ -1,30 +1,28 @@
|
||||
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::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_tools::CStringExt;
|
||||
use crate::dc_tools::{as_str, to_cstring};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::types::*;
|
||||
use crate::x::free;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -698,16 +705,26 @@ impl Imap {
|
||||
|
||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
if let Some(entry) = (self.get_config)(context, &key) {
|
||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
)
|
||||
} else {
|
||||
(0, 0)
|
||||
let val1 = unsafe {
|
||||
let key_c = to_cstring(key);
|
||||
let val = (self.get_config)(context, key_c, 0 as *const libc::c_char);
|
||||
free(key_c as *mut _);
|
||||
val
|
||||
};
|
||||
if val1.is_null() {
|
||||
return (0, 0);
|
||||
}
|
||||
let entry = as_str(val1);
|
||||
|
||||
if entry.is_empty() {
|
||||
return (0, 0);
|
||||
}
|
||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
|
||||
)
|
||||
}
|
||||
|
||||
fn fetch_from_single_folder<S: AsRef<str>>(&self, context: &Context, folder: S) -> usize {
|
||||
@@ -770,7 +787,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 +832,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;
|
||||
}
|
||||
}
|
||||
@@ -836,8 +853,10 @@ impl Imap {
|
||||
.expect("missing message id");
|
||||
|
||||
if 0 == unsafe {
|
||||
let message_id_c = CString::yolo(message_id);
|
||||
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
|
||||
let message_id_c = to_cstring(message_id);
|
||||
let res = (self.precheck_imf)(context, message_id_c, folder.as_ref(), cur_uid);
|
||||
free(message_id_c as *mut _);
|
||||
res
|
||||
} {
|
||||
// check passed, go fetch the rest
|
||||
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
|
||||
@@ -905,7 +924,13 @@ impl Imap {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||
|
||||
(self.set_config)(context, &key, Some(&val));
|
||||
unsafe {
|
||||
let key_c = to_cstring(key);
|
||||
let val_c = to_cstring(val);
|
||||
(self.set_config)(context, key_c, val_c);
|
||||
free(key_c as *mut _);
|
||||
free(val_c as *mut _);
|
||||
};
|
||||
}
|
||||
|
||||
fn fetch_single_msg<S: AsRef<str>>(
|
||||
@@ -921,13 +946,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 +964,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 +1022,11 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
1
|
||||
if retry_later {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idle(&self, context: &Context) {
|
||||
@@ -1054,7 +1091,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 +1594,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.",);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1588,25 +1622,18 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.set_config_int(context, "folders_configured", 3)
|
||||
.ok();
|
||||
context.sql.set_config_int(context, "folders_configured", 3);
|
||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||
.ok();
|
||||
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder));
|
||||
}
|
||||
if let Some(ref sentbox_folder) = sentbox_folder {
|
||||
context
|
||||
.sql
|
||||
.set_config(
|
||||
context,
|
||||
"configured_sentbox_folder",
|
||||
Some(sentbox_folder.name()),
|
||||
)
|
||||
.ok();
|
||||
context.sql.set_config(
|
||||
context,
|
||||
"configured_sentbox_folder",
|
||||
Some(sentbox_folder.name()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1131
src/job.rs
1131
src/job.rs
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
27
src/key.rs
27
src/key.rs
@@ -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 {
|
||||
@@ -216,16 +216,22 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each header line must be terminated by `\r\n`
|
||||
pub fn to_asc(&self, header: Option<(&str, &str)>) -> String {
|
||||
/// Each header line must be terminated by `\r\n`, the result must be freed.
|
||||
pub fn to_asc_c(&self, header: Option<(&str, &str)>) -> *mut libc::c_char {
|
||||
let headers = header.map(|(key, value)| {
|
||||
let mut m = BTreeMap::new();
|
||||
m.insert(key.to_string(), value.to_string());
|
||||
m
|
||||
});
|
||||
|
||||
self.to_armored_string(headers.as_ref())
|
||||
.expect("failed to serialize key")
|
||||
let buf = self
|
||||
.to_armored_string(headers.as_ref())
|
||||
.expect("failed to serialize key");
|
||||
let buf_c = CString::new(buf).unwrap();
|
||||
|
||||
// need to use strdup to allocate the result with malloc
|
||||
// so it can be `free`d later.
|
||||
unsafe { strdup(buf_c.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
|
||||
@@ -233,16 +239,15 @@ impl Key {
|
||||
return false;
|
||||
}
|
||||
|
||||
let file_content = self.to_asc(None);
|
||||
let file_content_c = CString::new(file_content).unwrap();
|
||||
let file_content = self.to_asc_c(None);
|
||||
|
||||
let success = if 0
|
||||
== unsafe {
|
||||
dc_write_file(
|
||||
context,
|
||||
file,
|
||||
file_content_c.as_ptr() as *const libc::c_void,
|
||||
file_content_c.as_bytes().len(),
|
||||
file_content as *const libc::c_void,
|
||||
strlen(file_content),
|
||||
)
|
||||
} {
|
||||
error!(context, 0, "Cannot write key to {}", to_string(file));
|
||||
@@ -251,6 +256,8 @@ impl Key {
|
||||
true
|
||||
};
|
||||
|
||||
unsafe { free(file_content as *mut libc::c_void) };
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
@@ -283,7 +290,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
src/keyhistory.rs
Normal file
13
src/keyhistory.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use crate::context::Context;
|
||||
|
||||
/* yes: uppercase */
|
||||
/* library private: key-history */
|
||||
pub fn dc_add_to_keyhistory(
|
||||
_context: &Context,
|
||||
_rfc724_mid: *const libc::c_char,
|
||||
_sending_time: u64,
|
||||
_addr: *const libc::c_char,
|
||||
_fingerprint: *const libc::c_char,
|
||||
) {
|
||||
|
||||
}
|
||||
72
src/lib.rs
72
src/lib.rs
@@ -1,9 +1,12 @@
|
||||
#![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)]
|
||||
#![allow(
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
non_upper_case_globals,
|
||||
non_camel_case_types,
|
||||
non_snake_case
|
||||
)]
|
||||
#![feature(c_variadic, ptr_wrapping_offset_from)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
@@ -13,56 +16,53 @@ extern crate num_derive;
|
||||
extern crate smallvec;
|
||||
#[macro_use]
|
||||
extern crate rusqlite;
|
||||
extern crate strum;
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
mod aheader;
|
||||
pub mod chat;
|
||||
pub mod chatlist;
|
||||
pub mod aheader;
|
||||
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 error;
|
||||
pub mod imap;
|
||||
pub mod key;
|
||||
pub mod keyhistory;
|
||||
pub mod keyring;
|
||||
pub mod location;
|
||||
pub mod lot;
|
||||
pub mod message;
|
||||
pub mod oauth2;
|
||||
mod param;
|
||||
pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod qr;
|
||||
mod smtp;
|
||||
pub mod smtp;
|
||||
pub mod sql;
|
||||
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_chatlist;
|
||||
pub mod dc_configure;
|
||||
pub mod dc_contact;
|
||||
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_param;
|
||||
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_stock;
|
||||
pub mod dc_strencode;
|
||||
pub mod dc_token;
|
||||
pub mod dc_tools;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
pub use self::constants::*;
|
||||
|
||||
696
src/location.rs
696
src/location.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
20
src/log.rs
20
src/log.rs
@@ -7,9 +7,10 @@ macro_rules! info {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||
$ctx.call_cb($crate::constants::Event::INFO, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
formatted_c as libc::uintptr_t);
|
||||
libc::free(formatted_c as *mut libc::c_void);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -22,9 +23,10 @@ macro_rules! warn {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
formatted_c as libc::uintptr_t);
|
||||
libc::free(formatted_c as *mut libc::c_void) ;
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -37,9 +39,10 @@ macro_rules! error {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
formatted_c as libc::uintptr_t);
|
||||
libc::free(formatted_c as *mut libc::c_void);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -52,8 +55,9 @@ macro_rules! log_event {
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||
$ctx.call_cb($event, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
formatted_c as libc::uintptr_t);
|
||||
libc::free(formatted_c as *mut libc::c_void);
|
||||
}};
|
||||
}
|
||||
|
||||
109
src/lot.rs
109
src/lot.rs
@@ -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
|
||||
}
|
||||
}
|
||||
1330
src/message.rs
1330
src/message.rs
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::context::Context;
|
||||
@@ -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,
|
||||
@@ -48,17 +48,11 @@ pub fn dc_get_oauth2_url(
|
||||
redirect_uri: impl AsRef<str>,
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
if context
|
||||
.sql
|
||||
.set_config(
|
||||
context,
|
||||
"oauth2_pending_redirect_uri",
|
||||
Some(redirect_uri.as_ref()),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
context.sql.set_config(
|
||||
context,
|
||||
"oauth2_pending_redirect_uri",
|
||||
Some(redirect_uri.as_ref()),
|
||||
);
|
||||
let oauth2_url = replace_in_uri(&oauth2.get_code, "$CLIENT_ID", &oauth2.client_id);
|
||||
let oauth2_url = replace_in_uri(&oauth2_url, "$REDIRECT_URI", redirect_uri.as_ref());
|
||||
|
||||
@@ -163,12 +157,10 @@ pub fn dc_get_oauth2_access_token(
|
||||
if let Some(ref token) = response.refresh_token {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_refresh_token", Some(token))
|
||||
.ok();
|
||||
.set_config(context, "oauth2_refresh_token", Some(token));
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.ok();
|
||||
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()));
|
||||
}
|
||||
|
||||
// after that, save the access token.
|
||||
@@ -176,8 +168,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
if let Some(ref token) = response.access_token {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_access_token", Some(token))
|
||||
.ok();
|
||||
.set_config(context, "oauth2_access_token", Some(token));
|
||||
let expires_in = response
|
||||
.expires_in
|
||||
// refresh a bet before
|
||||
@@ -185,14 +176,12 @@ pub fn dc_get_oauth2_access_token(
|
||||
.unwrap_or_else(|| 0);
|
||||
context
|
||||
.sql
|
||||
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.ok();
|
||||
.set_config_int64(context, "oauth2_timestamp_expires", expires_in);
|
||||
|
||||
if update_redirect_uri_on_success {
|
||||
context
|
||||
.sql
|
||||
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.ok();
|
||||
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()));
|
||||
}
|
||||
} else {
|
||||
warn!(context, 0, "Failed to find OAuth2 access token");
|
||||
@@ -321,7 +310,7 @@ fn is_expired(context: &Context) -> bool {
|
||||
}
|
||||
|
||||
fn replace_in_uri(uri: impl AsRef<str>, key: impl AsRef<str>, value: impl AsRef<str>) -> String {
|
||||
let value_urlencoded = utf8_percent_encode(value.as_ref(), NON_ALPHANUMERIC).to_string();
|
||||
let value_urlencoded = utf8_percent_encode(value.as_ref(), DEFAULT_ENCODE_SET).to_string();
|
||||
uri.as_ref().replace(key.as_ref(), &value_urlencoded)
|
||||
}
|
||||
|
||||
@@ -344,7 +333,7 @@ mod tests {
|
||||
fn test_replace_in_uri() {
|
||||
assert_eq!(
|
||||
replace_in_uri("helloworld", "world", "a-b c"),
|
||||
"helloa%2Db%20c"
|
||||
"helloa-b%20c"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
246
src/param.rs
246
src/param.rs
@@ -1,246 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// Available param keys.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, PartialOrd, Ord, FromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum Param {
|
||||
/// For messages and jobs
|
||||
File = 'f' as u8,
|
||||
/// For Messages
|
||||
Width = 'w' as u8,
|
||||
/// For Messages
|
||||
Height = 'h' as u8,
|
||||
/// For Messages
|
||||
Duration = 'd' as u8,
|
||||
/// For Messages
|
||||
MimeType = 'm' as u8,
|
||||
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||
GuranteeE2ee = 'c' as u8,
|
||||
/// For Messages: decrypted with validation errors or without mutual set, if neither
|
||||
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
|
||||
ErroneousE2ee = 'e' as u8,
|
||||
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
||||
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
||||
ForcePlaintext = 'u' as u8,
|
||||
/// For Messages
|
||||
WantsMdn = 'r' as u8,
|
||||
/// For Messages
|
||||
Forwarded = 'a' as u8,
|
||||
/// For Messages
|
||||
Cmd = 'S' as u8,
|
||||
/// For Messages
|
||||
Arg = 'E' as u8,
|
||||
/// For Messages
|
||||
Arg2 = 'F' as u8,
|
||||
/// For Messages
|
||||
Arg3 = 'G' as u8,
|
||||
/// For Messages
|
||||
Arg4 = 'H' as u8,
|
||||
/// For Messages
|
||||
Error = 'L' as u8,
|
||||
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
||||
PrepForwards = 'P' as u8,
|
||||
/// For Jobs
|
||||
SetLatitude = 'l' as u8,
|
||||
/// For Jobs
|
||||
SetLongitude = 'n' as u8,
|
||||
/// For Jobs
|
||||
ServerFolder = 'Z' as u8,
|
||||
/// For Jobs
|
||||
ServerUid = 'z' as u8,
|
||||
/// For Jobs
|
||||
AlsoMove = 'M' as u8,
|
||||
/// For Jobs: space-separated list of message recipients
|
||||
Recipients = 'R' as u8,
|
||||
// For Groups
|
||||
Unpromoted = 'U' as u8,
|
||||
// For Groups and Contacts
|
||||
ProfileImage = 'i' as u8,
|
||||
// For Chats
|
||||
Selftalk = 'K' as u8,
|
||||
// For QR
|
||||
Auth = 's' as u8,
|
||||
// For QR
|
||||
GroupId = 'x' as u8,
|
||||
// For QR
|
||||
GroupName = 'g' as u8,
|
||||
}
|
||||
|
||||
/// Possible values for `Param::ForcePlaintext`.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum ForcePlaintext {
|
||||
AddAutocryptHeader = 1,
|
||||
NoAutocryptHeader = 2,
|
||||
}
|
||||
|
||||
/// An object for handling key=value parameter lists.
|
||||
///
|
||||
/// The structure is serialized by calling `to_string()` on it.
|
||||
///
|
||||
/// Only for library-internal use.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Params {
|
||||
inner: BTreeMap<Param, String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Params {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, (key, value)) in self.inner.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, "\n")?;
|
||||
}
|
||||
write!(f, "{}={}", *key as u8 as char, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Params {
|
||||
type Err = error::Error;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut inner = BTreeMap::new();
|
||||
for pair in s.trim().lines() {
|
||||
let pair = pair.trim();
|
||||
if pair.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// TODO: probably nicer using a regex
|
||||
ensure!(pair.len() > 1, "Invalid key pair: '{}'", pair);
|
||||
let mut split = pair.splitn(2, '=');
|
||||
let key = split.next();
|
||||
let value = split.next();
|
||||
|
||||
ensure!(key.is_some(), "Missing key");
|
||||
ensure!(value.is_some(), "Missing value");
|
||||
|
||||
let key = key.unwrap().trim();
|
||||
let value = value.unwrap().trim();
|
||||
|
||||
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
|
||||
inner.insert(key, value.to_string());
|
||||
} else {
|
||||
bail!("Unknown key: {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Params { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl Params {
|
||||
/// Create new empty params.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Get the value of the given key, return `None` if no value is set.
|
||||
pub fn get(&self, key: Param) -> Option<&str> {
|
||||
self.inner.get(&key).map(|s| s.as_str())
|
||||
}
|
||||
|
||||
/// Check if the given key is set.
|
||||
pub fn exists(&self, key: Param) -> bool {
|
||||
self.inner.contains_key(&key)
|
||||
}
|
||||
|
||||
/// Set the given key to the passed in value.
|
||||
pub fn set(&mut self, key: Param, value: impl AsRef<str>) -> &mut Self {
|
||||
self.inner.insert(key, value.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes the given key, if it exists.
|
||||
pub fn remove(&mut self, key: Param) -> &mut Self {
|
||||
self.inner.remove(&key);
|
||||
self
|
||||
}
|
||||
|
||||
/// Check if there are any values in this.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
/// Returns how many key-value pairs are set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
/// Get the given parameter and parse as `i32`.
|
||||
pub fn get_int(&self, key: Param) -> Option<i32> {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
/// Get the given parameter and parse as `f64`.
|
||||
pub fn get_float(&self, key: Param) -> Option<f64> {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
/// Set the given paramter to the passed in `i32`.
|
||||
pub fn set_int(&mut self, key: Param, value: i32) -> &mut Self {
|
||||
self.set(key, format!("{}", value));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the given parameter to the passed in `f64` .
|
||||
pub fn set_float(&mut self, key: Param, value: f64) -> &mut Self {
|
||||
self.set(key, format!("{}", value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dc_param() {
|
||||
let mut p1: Params = "\r\n\r\na=1\nf=2\n\nc = 3 ".parse().unwrap();
|
||||
|
||||
assert_eq!(p1.get_int(Param::Forwarded), Some(1));
|
||||
assert_eq!(p1.get_int(Param::File), Some(2));
|
||||
assert_eq!(p1.get_int(Param::Height), None);
|
||||
assert!(!p1.exists(Param::Height));
|
||||
|
||||
p1.set_int(Param::Duration, 4);
|
||||
|
||||
assert_eq!(p1.get_int(Param::Duration), Some(4));
|
||||
|
||||
let mut p1 = Params::new();
|
||||
|
||||
p1.set(Param::Forwarded, "foo")
|
||||
.set_int(Param::File, 2)
|
||||
.remove(Param::GuranteeE2ee)
|
||||
.set_int(Param::Duration, 4);
|
||||
|
||||
assert_eq!(p1.to_string(), "a=foo\nd=4\nf=2");
|
||||
|
||||
p1.remove(Param::File);
|
||||
|
||||
assert_eq!(p1.to_string(), "a=foo\nd=4",);
|
||||
assert_eq!(p1.len(), 2);
|
||||
|
||||
p1.remove(Param::Forwarded);
|
||||
p1.remove(Param::Duration);
|
||||
|
||||
assert_eq!(p1.to_string(), "",);
|
||||
|
||||
assert!(p1.is_empty());
|
||||
assert_eq!(p1.len(), 0)
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
117
src/peerstate.rs
117
src/peerstate.rs
@@ -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
|
||||
@@ -489,11 +462,16 @@ mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use tempfile::TempDir;
|
||||
use std::ffi::CStr;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::to_cstring;
|
||||
use crate::x::free;
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from_base64("xsBNBFztUVkBCADYaQl/UOUpRPd32nLRzx8eU0eI+jQEnG+g5anjYA+3oct1rROGl5SygjMULDKdaUy27O3o9Srsti0YjA7uxZnavIqhSopJhFidqY1M1wA9JZa/duucZdNwUGbjGIRsS/4Cjr5+3svscK24hVYub1dvDWXpwUTnj3K6xOEnJdoM+MhCqtSD5+zcJhFc9vyZm9ZTGWUxAhKh0iJTcCD8V6CQ3XZ2z9GruwzZT/FTFovWrz7m3TUI2OdSSHh0eZLRGEoxMCT/vzflAFGAr8ijCaRsEIfqP6FW8uQWnFTqkjxEUCZG6XkeFHB84aj5jqYG/1KCLjL5vEKwfl1tz/WnPhY7ABEBAAHNEDxoZWxsb0BtYWlsLmNvbT7CwIkEEAEIADMCGQEFAlztUVoCGwMECwkIBwYVCAkKCwIDFgIBFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7ijAwf+PTsuawUax9cNPn1bN90H+g9qyHZJMEwKXtUnNaXJxPW3iB7ThhpCiCzsZwP7+l7ArS8tmLeNDw2bENtcf1XCv4wovP2fdXOP3QOUUFX/GdakcTwv7DzC7CO0grB1HtaPhGw/6UX2o2cx2i9xiUf4Givq2MfCbgAW5zloH6WXGPb6yLQYJXxqDIphr4+uZDb+bMAyWHN/DUkAjHrV8nnVki7PMHqzzZpwglalxMX8RGeiGZE39ALJKL/Og87DMFah87/yoxQWGoS7Wqv0XDcCPKoTCPrpk8pOe2KEsq/lz215nefHd4aRpfUX5YCYa8HPvvfPQbGF73uvyQw5w7qjis7ATQRc7VFZAQgAt8ONdnX6KEEQ5Jw6ilJ+LBtY44SP5t0I3eK+goKepgIiKhjGDa+Mntyi4jdhH+HO6kvK5SHMh2sPp4rRO/WKHJwWFySyM1OdyiywhyH0J9R5rBY4vPHsJjf6vSKJdWLWT+ho1fNet2IIC+jVCYli91MAMbRvk6EKVj1nCc+67giOahXEkHt6xxkeCGlOvbw8hxGj1A8+AC1BLms/OR3oc4JMi9O3kq6uG0z9tlUEerac9HVwcjoO1XLe+hJhoT5H+TbnGjPuhuURP3pFiIKHpbRYgUfdSAY0dTObO7t4I5y/drPOrCTnWrBUg2wXAECUhpRKow9/ai2YemLv9KqhhwARAQABwsB2BBgBCAAgBQJc7VFaAhsMFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7jmyggAhs4QzCzIbT2OsAReBxkxtm0AI+g1HZ1KFKof5NDHfgv9C/Qu1I8mKEjlZzA4qFyPmLqntgwJ0RuFy6gLbljZBNCFO7vB478AhYtnWjuKZmA40HUPwcB1hEJ31c42akzfUbioY1TLLepngdsJg7Cm8O+rhI9+1WRA66haJDgFs793SVUDyJh8f9NX50l5zR87/bsV30CFSw0q4OSSy9VI/z+2g5khn1LnuuOrCfFnYIPYtJED1BfkXkosxGlgbzy79VvGmI9d23x4atDK7oBPCzIj+lP8sytJ0u3HOguXi9OgDitKy+Pt1r8gH8frdktMJr5Ts6DW+tIn2vR23KR8aA==", KeyType::Public).unwrap();
|
||||
@@ -525,44 +503,35 @@ 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 {
|
||||
ctx: Context,
|
||||
dir: TempDir,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cb(
|
||||
_context: &Context,
|
||||
_event: Event,
|
||||
_data1: libc::uintptr_t,
|
||||
_data2: libc::uintptr_t,
|
||||
) -> libc::uintptr_t {
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn create_test_context() -> TestContext {
|
||||
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = to_cstring(dir.path().join("db.sqlite").to_str().unwrap());
|
||||
assert_eq!(
|
||||
dc_open(&mut ctx, dbfile, std::ptr::null()),
|
||||
1,
|
||||
"Failed to open {}",
|
||||
CStr::from_ptr(dbfile as *const _).to_str().unwrap()
|
||||
);
|
||||
|
||||
free(dbfile as *mut _);
|
||||
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
}
|
||||
|
||||
22
src/pgp.rs
22
src/pgp.rs
@@ -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,
|
||||
@@ -18,31 +17,32 @@ use crate::keyring::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
// TODO should return bool /rtn
|
||||
pub unsafe fn dc_split_armored_data(
|
||||
buf: *mut libc::c_char,
|
||||
ret_headerline: *mut *const libc::c_char,
|
||||
ret_setupcodebegin: *mut *const libc::c_char,
|
||||
ret_preferencrypt: *mut *const libc::c_char,
|
||||
ret_base64: *mut *const libc::c_char,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
) -> libc::c_int {
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let mut line_chars: size_t = 0i32 as size_t;
|
||||
let mut line: *mut libc::c_char = buf;
|
||||
let mut p1: *mut libc::c_char = buf;
|
||||
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);
|
||||
@@ -128,7 +128,7 @@ pub unsafe fn dc_split_armored_data(
|
||||
if !ret_base64.is_null() {
|
||||
*ret_base64 = base64
|
||||
}
|
||||
success = true;
|
||||
success = 1i32
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,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
469
src/qr.rs
@@ -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!(¶m);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
191
src/sql.rs
191
src/sql.rs
@@ -4,11 +4,13 @@ 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_param::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::x::*;
|
||||
|
||||
const DC_OPEN_READONLY: usize = 0x01;
|
||||
|
||||
@@ -139,7 +141,6 @@ impl Sql {
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a query which is expected to return one row.
|
||||
pub fn query_row<T, P, F>(&self, sql: impl AsRef<str>, params: P, f: F) -> Result<T>
|
||||
where
|
||||
P: IntoIterator,
|
||||
@@ -183,9 +184,7 @@ impl Sql {
|
||||
}
|
||||
|
||||
/// Set private configuration options.
|
||||
///
|
||||
/// Setting `None` deletes the value. On failure an error message
|
||||
/// will already have been logged.
|
||||
/// Setting `None` deletes the value.
|
||||
pub fn set_config(
|
||||
&self,
|
||||
context: &Context,
|
||||
@@ -286,7 +285,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(())
|
||||
@@ -338,7 +337,7 @@ fn open(
|
||||
info!(
|
||||
context,
|
||||
0,
|
||||
"First time init: creating tables in {:?}.",
|
||||
"First time init: creating tables in \"{:?}\".",
|
||||
dbfile.as_ref(),
|
||||
);
|
||||
sql.execute(
|
||||
@@ -460,7 +459,7 @@ fn open(
|
||||
// cannot create the tables - maybe we cannot write?
|
||||
return Err(Error::SqlFailedToOpen);
|
||||
} else {
|
||||
sql.set_config_int(context, "dbversion", 0)?;
|
||||
sql.set_config_int(context, "dbversion", 0);
|
||||
}
|
||||
} else {
|
||||
exists_before_update = 1;
|
||||
@@ -485,7 +484,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 1;
|
||||
sql.set_config_int(context, "dbversion", 1)?;
|
||||
sql.set_config_int(context, "dbversion", 1);
|
||||
}
|
||||
if dbversion < 2 {
|
||||
sql.execute(
|
||||
@@ -493,7 +492,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 2;
|
||||
sql.set_config_int(context, "dbversion", 2)?;
|
||||
sql.set_config_int(context, "dbversion", 2);
|
||||
}
|
||||
if dbversion < 7 {
|
||||
sql.execute(
|
||||
@@ -507,7 +506,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 7;
|
||||
sql.set_config_int(context, "dbversion", 7)?;
|
||||
sql.set_config_int(context, "dbversion", 7);
|
||||
}
|
||||
if dbversion < 10 {
|
||||
sql.execute(
|
||||
@@ -525,7 +524,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 10;
|
||||
sql.set_config_int(context, "dbversion", 10)?;
|
||||
sql.set_config_int(context, "dbversion", 10);
|
||||
}
|
||||
if dbversion < 12 {
|
||||
sql.execute(
|
||||
@@ -537,7 +536,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 12;
|
||||
sql.set_config_int(context, "dbversion", 12)?;
|
||||
sql.set_config_int(context, "dbversion", 12);
|
||||
}
|
||||
if dbversion < 17 {
|
||||
sql.execute(
|
||||
@@ -551,7 +550,7 @@ fn open(
|
||||
)?;
|
||||
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?;
|
||||
dbversion = 17;
|
||||
sql.set_config_int(context, "dbversion", 17)?;
|
||||
sql.set_config_int(context, "dbversion", 17);
|
||||
}
|
||||
if dbversion < 18 {
|
||||
sql.execute(
|
||||
@@ -560,7 +559,7 @@ fn open(
|
||||
)?;
|
||||
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?;
|
||||
dbversion = 18;
|
||||
sql.set_config_int(context, "dbversion", 18)?;
|
||||
sql.set_config_int(context, "dbversion", 18);
|
||||
}
|
||||
if dbversion < 27 {
|
||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?;
|
||||
@@ -577,7 +576,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 27;
|
||||
sql.set_config_int(context, "dbversion", 27)?;
|
||||
sql.set_config_int(context, "dbversion", 27);
|
||||
}
|
||||
if dbversion < 34 {
|
||||
sql.execute(
|
||||
@@ -606,7 +605,7 @@ fn open(
|
||||
)?;
|
||||
recalc_fingerprints = 1;
|
||||
dbversion = 34;
|
||||
sql.set_config_int(context, "dbversion", 34)?;
|
||||
sql.set_config_int(context, "dbversion", 34);
|
||||
}
|
||||
if dbversion < 39 {
|
||||
sql.execute(
|
||||
@@ -636,7 +635,7 @@ fn open(
|
||||
)?;
|
||||
}
|
||||
dbversion = 39;
|
||||
sql.set_config_int(context, "dbversion", 39)?;
|
||||
sql.set_config_int(context, "dbversion", 39);
|
||||
}
|
||||
if dbversion < 40 {
|
||||
sql.execute(
|
||||
@@ -644,22 +643,22 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 40;
|
||||
sql.set_config_int(context, "dbversion", 40)?;
|
||||
sql.set_config_int(context, "dbversion", 40);
|
||||
}
|
||||
if dbversion < 41 {
|
||||
update_file_paths = 1;
|
||||
dbversion = 41;
|
||||
sql.set_config_int(context, "dbversion", 41)?;
|
||||
sql.set_config_int(context, "dbversion", 41);
|
||||
}
|
||||
if dbversion < 42 {
|
||||
sql.execute("UPDATE msgs SET txt='' WHERE type!=10", params![])?;
|
||||
dbversion = 42;
|
||||
sql.set_config_int(context, "dbversion", 42)?;
|
||||
sql.set_config_int(context, "dbversion", 42);
|
||||
}
|
||||
if dbversion < 44 {
|
||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?;
|
||||
dbversion = 44;
|
||||
sql.set_config_int(context, "dbversion", 44)?;
|
||||
sql.set_config_int(context, "dbversion", 44);
|
||||
}
|
||||
if dbversion < 46 {
|
||||
sql.execute(
|
||||
@@ -671,7 +670,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 46;
|
||||
sql.set_config_int(context, "dbversion", 46)?;
|
||||
sql.set_config_int(context, "dbversion", 46);
|
||||
}
|
||||
if dbversion < 47 {
|
||||
info!(context, 0, "[migration] v47");
|
||||
@@ -680,7 +679,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 47;
|
||||
sql.set_config_int(context, "dbversion", 47)?;
|
||||
sql.set_config_int(context, "dbversion", 47);
|
||||
}
|
||||
if dbversion < 48 {
|
||||
info!(context, 0, "[migration] v48");
|
||||
@@ -688,9 +687,13 @@ 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)?;
|
||||
sql.set_config_int(context, "dbversion", 48);
|
||||
}
|
||||
if dbversion < 49 {
|
||||
info!(context, 0, "[migration] v49");
|
||||
@@ -699,15 +702,15 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 49;
|
||||
sql.set_config_int(context, "dbversion", 49)?;
|
||||
sql.set_config_int(context, "dbversion", 49);
|
||||
}
|
||||
if dbversion < 50 {
|
||||
info!(context, 0, "[migration] v50");
|
||||
if 0 != exists_before_update {
|
||||
sql.set_config_int(context, "show_emails", 2)?;
|
||||
sql.set_config_int(context, "show_emails", 2);
|
||||
}
|
||||
dbversion = 50;
|
||||
sql.set_config_int(context, "dbversion", 50)?;
|
||||
sql.set_config_int(context, "dbversion", 50);
|
||||
}
|
||||
if dbversion < 53 {
|
||||
info!(context, 0, "[migration] v53");
|
||||
@@ -740,7 +743,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
dbversion = 53;
|
||||
sql.set_config_int(context, "dbversion", 53)?;
|
||||
sql.set_config_int(context, "dbversion", 53);
|
||||
}
|
||||
if dbversion < 54 {
|
||||
info!(context, 0, "[migration] v54");
|
||||
@@ -750,7 +753,7 @@ fn open(
|
||||
)?;
|
||||
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?;
|
||||
dbversion = 54;
|
||||
sql.set_config_int(context, "dbversion", 54)?;
|
||||
sql.set_config_int(context, "dbversion", 54);
|
||||
}
|
||||
if dbversion < 55 {
|
||||
sql.execute(
|
||||
@@ -758,7 +761,7 @@ fn open(
|
||||
params![],
|
||||
)?;
|
||||
|
||||
sql.set_config_int(context, "dbversion", 55)?;
|
||||
sql.set_config_int(context, "dbversion", 55);
|
||||
}
|
||||
|
||||
if 0 != recalc_fingerprints {
|
||||
@@ -806,11 +809,11 @@ fn open(
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
||||
sql.set_config(context, "backup_for", None)?;
|
||||
sql.set_config(context, "backup_for", None);
|
||||
}
|
||||
}
|
||||
|
||||
info!(context, 0, "Opened {:?}.", dbfile.as_ref(),);
|
||||
info!(context, 0, "Opened \"{:?}\".", dbfile.as_ref(),);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -830,7 +833,7 @@ where
|
||||
&err,
|
||||
querystr.as_ref()
|
||||
);
|
||||
Err(err)
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -948,25 +951,25 @@ pub fn housekeeping(context: &Context) {
|
||||
context,
|
||||
&mut files_in_use,
|
||||
"SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;",
|
||||
Param::File,
|
||||
'f' as i32,
|
||||
);
|
||||
maybe_add_from_param(
|
||||
context,
|
||||
&mut files_in_use,
|
||||
"SELECT param FROM jobs;",
|
||||
Param::File,
|
||||
'f' as i32,
|
||||
);
|
||||
maybe_add_from_param(
|
||||
context,
|
||||
&mut files_in_use,
|
||||
"SELECT param FROM chats;",
|
||||
Param::ProfileImage,
|
||||
'i' as i32,
|
||||
);
|
||||
maybe_add_from_param(
|
||||
context,
|
||||
&mut files_in_use,
|
||||
"SELECT param FROM contacts;",
|
||||
Param::ProfileImage,
|
||||
'i' as i32,
|
||||
);
|
||||
|
||||
context
|
||||
@@ -1001,15 +1004,35 @@ pub fn housekeeping(context: &Context) {
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let name_f = entry.file_name();
|
||||
let name_s = name_f.to_string_lossy();
|
||||
let name_c = unsafe { to_cstring(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 unsafe { is_file_in_use(&mut files_in_use, 0 as *const libc::c_char, name_c) }
|
||||
|| unsafe {
|
||||
is_file_in_use(
|
||||
&mut files_in_use,
|
||||
b".increation\x00" as *const u8 as *const libc::c_char,
|
||||
name_c,
|
||||
)
|
||||
}
|
||||
|| unsafe {
|
||||
is_file_in_use(
|
||||
&mut files_in_use,
|
||||
b".waveform\x00" as *const u8 as *const libc::c_char,
|
||||
name_c,
|
||||
)
|
||||
}
|
||||
|| unsafe {
|
||||
is_file_in_use(
|
||||
&mut files_in_use,
|
||||
b"-preview.jpg\x00" as *const u8 as *const libc::c_char,
|
||||
name_c,
|
||||
)
|
||||
}
|
||||
{
|
||||
unsafe { free(name_c as *mut _) };
|
||||
continue;
|
||||
}
|
||||
unsafe { free(name_c as *mut _) };
|
||||
|
||||
unreferenced_count += 1;
|
||||
|
||||
@@ -1042,8 +1065,11 @@ pub fn housekeeping(context: &Context) {
|
||||
unreferenced_count,
|
||||
entry.file_name()
|
||||
);
|
||||
let path = entry.path();
|
||||
dc_delete_file(context, path);
|
||||
unsafe {
|
||||
let path = to_cstring(entry.path().to_str().unwrap());
|
||||
dc_delete_file(context, path);
|
||||
free(path as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1060,18 +1086,26 @@ pub fn housekeeping(context: &Context) {
|
||||
info!(context, 0, "Housekeeping done.",);
|
||||
}
|
||||
|
||||
fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
|
||||
let name_to_check = if let Some(namespc) = namespc_opt {
|
||||
let name_len = name.len();
|
||||
let namespc_len = namespc.len();
|
||||
if name_len <= namespc_len || !name.ends_with(namespc) {
|
||||
unsafe fn is_file_in_use(
|
||||
files_in_use: &HashSet<String>,
|
||||
namespc: *const libc::c_char,
|
||||
name: *const libc::c_char,
|
||||
) -> bool {
|
||||
let name_to_check = dc_strdup(name);
|
||||
if !namespc.is_null() {
|
||||
let name_len: libc::c_int = strlen(name) as libc::c_int;
|
||||
let namespc_len: libc::c_int = strlen(namespc) as libc::c_int;
|
||||
if name_len <= namespc_len
|
||||
|| strcmp(&*name.offset((name_len - namespc_len) as isize), namespc) != 0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
&name[..name_len - namespc_len]
|
||||
} else {
|
||||
name
|
||||
};
|
||||
files_in_use.contains(name_to_check)
|
||||
*name_to_check.offset((name_len - namespc_len) as isize) = 0 as libc::c_char
|
||||
}
|
||||
|
||||
let contains = files_in_use.contains(as_str(name_to_check));
|
||||
free(name_to_check as *mut libc::c_void);
|
||||
contains
|
||||
}
|
||||
|
||||
fn maybe_add_file(files_in_use: &mut HashSet<String>, file: impl AsRef<str>) {
|
||||
@@ -1086,20 +1120,31 @@ fn maybe_add_from_param(
|
||||
context: &Context,
|
||||
files_in_use: &mut HashSet<String>,
|
||||
query: &str,
|
||||
param_id: Param,
|
||||
param_id: libc::c_int,
|
||||
) {
|
||||
let param = unsafe { dc_param_new() };
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_row(query, NO_PARAMS, |row| {
|
||||
let param: Params = row.get::<_, String>(0)?.parse().unwrap_or_default();
|
||||
if let Some(file) = param.get(param_id) {
|
||||
maybe_add_file(files_in_use, file);
|
||||
unsafe {
|
||||
let v = to_cstring(row.get::<_, String>(0)?);
|
||||
dc_param_set_packed(param, v as *const _);
|
||||
let file = dc_param_get(param, param_id, 0 as *const _);
|
||||
if !file.is_null() {
|
||||
maybe_add_file(files_in_use, as_str(file));
|
||||
free(file as *mut libc::c_void);
|
||||
}
|
||||
|
||||
free(v as *mut _);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(context, 0, "sql: failed to add_from_param: {}", err);
|
||||
});
|
||||
|
||||
unsafe { dc_param_unref(param) };
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1125,12 +1170,26 @@ mod test {
|
||||
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
|
||||
maybe_add_file(&mut files, "world2.txt");
|
||||
|
||||
assert!(is_file_in_use(&mut files, None, "hello"));
|
||||
assert!(!is_file_in_use(&mut files, Some(".txt"), "hello"));
|
||||
assert!(is_file_in_use(
|
||||
&mut files,
|
||||
Some("-suffix"),
|
||||
"world.txt-suffix"
|
||||
));
|
||||
assert!(unsafe {
|
||||
is_file_in_use(
|
||||
&mut files,
|
||||
std::ptr::null(),
|
||||
b"hello\x00" as *const u8 as *const _,
|
||||
)
|
||||
});
|
||||
assert!(!unsafe {
|
||||
is_file_in_use(
|
||||
&mut files,
|
||||
b".txt\x00" as *const u8 as *const _,
|
||||
b"hello\x00" as *const u8 as *const _,
|
||||
)
|
||||
});
|
||||
assert!(unsafe {
|
||||
is_file_in_use(
|
||||
&mut files,
|
||||
b"-suffix\x00" as *const u8 as *const _,
|
||||
b"world.txt-suffix\x00" as *const u8 as *const _,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
394
src/stock.rs
394
src/stock.rs
@@ -1,394 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use strum::EnumProperty;
|
||||
use strum_macros::EnumProperty;
|
||||
|
||||
use crate::constants::Event;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use libc::free;
|
||||
|
||||
/// Stock strings
|
||||
///
|
||||
/// These identify the string to return in [Context.stock_str]. The
|
||||
/// numbers must stay in sync with `deltachat.h` `DC_STR_*` constants.
|
||||
///
|
||||
/// See the `stock_*` methods on [Context] to use these.
|
||||
///
|
||||
/// [Context]: crate::context::Context
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, EnumProperty)]
|
||||
#[repr(u32)]
|
||||
pub enum StockMessage {
|
||||
#[strum(props(fallback = "No messages."))]
|
||||
NoMessages = 1,
|
||||
#[strum(props(fallback = "Me"))]
|
||||
SelfMsg = 2,
|
||||
#[strum(props(fallback = "Draft"))]
|
||||
Draft = 3,
|
||||
#[strum(props(fallback = "%1$s member(s)"))]
|
||||
Member = 4,
|
||||
#[strum(props(fallback = "%1$s contact(s)"))]
|
||||
Contact = 6,
|
||||
#[strum(props(fallback = "Voice message"))]
|
||||
VoiceMessage = 7,
|
||||
#[strum(props(fallback = "Contact requests"))]
|
||||
DeadDrop = 8,
|
||||
#[strum(props(fallback = "Image"))]
|
||||
Image = 9,
|
||||
#[strum(props(fallback = "Video"))]
|
||||
Video = 10,
|
||||
#[strum(props(fallback = "Audio"))]
|
||||
Audio = 11,
|
||||
#[strum(props(fallback = "File"))]
|
||||
File = 12,
|
||||
#[strum(props(fallback = "Sent with my Delta Chat Messenger: https://delta.chat"))]
|
||||
StatusLine = 13,
|
||||
#[strum(props(fallback = "Hello, I\'ve just created the group \"%1$s\" for us."))]
|
||||
NewGroupDraft = 14,
|
||||
#[strum(props(fallback = "Group name changed from \"%1$s\" to \"%2$s\"."))]
|
||||
MsgGrpName = 15,
|
||||
#[strum(props(fallback = "Group image changed."))]
|
||||
MsgGrpImgChanged = 16,
|
||||
#[strum(props(fallback = "Member %1$s added."))]
|
||||
MsgAddMember = 17,
|
||||
#[strum(props(fallback = "Member %1$s removed."))]
|
||||
MsgDelMember = 18,
|
||||
#[strum(props(fallback = "Group left."))]
|
||||
MsgGroupLeft = 19,
|
||||
#[strum(props(fallback = "GIF"))]
|
||||
Gif = 23,
|
||||
#[strum(props(fallback = "Encrypted message"))]
|
||||
EncryptedMsg = 24,
|
||||
#[strum(props(fallback = "End-to-end encryption available."))]
|
||||
E2eAvailable = 25,
|
||||
#[strum(props(fallback = "Transport-encryption."))]
|
||||
EncrTransp = 27,
|
||||
#[strum(props(fallback = "No encryption."))]
|
||||
EncrNone = 28,
|
||||
#[strum(props(fallback = "This message was encrypted for another setup."))]
|
||||
CantDecryptMsgBody = 29,
|
||||
#[strum(props(fallback = "Fingerprints"))]
|
||||
FingerPrints = 30,
|
||||
#[strum(props(fallback = "Return receipt"))]
|
||||
ReadRcpt = 31,
|
||||
#[strum(props(fallback = "This is a return receipt for the message \"%1$s\"."))]
|
||||
ReadRcptMailBody = 32,
|
||||
#[strum(props(fallback = "Group image deleted."))]
|
||||
MsgGrpImgDeleted = 33,
|
||||
#[strum(props(fallback = "End-to-end encryption preferred."))]
|
||||
E2ePreferred = 34,
|
||||
#[strum(props(fallback = "%1$s verified."))]
|
||||
ContactVerified = 35,
|
||||
#[strum(props(fallback = "Cannot verify %1$s"))]
|
||||
ContactNotVerified = 36,
|
||||
#[strum(props(fallback = "Changed setup for %1$s"))]
|
||||
ContactSetupChanged = 37,
|
||||
#[strum(props(fallback = "Archived chats"))]
|
||||
ArchivedChats = 40,
|
||||
#[strum(props(fallback = "Starred messages"))]
|
||||
StarredMsgs = 41,
|
||||
#[strum(props(fallback = "Autocrypt Setup Message"))]
|
||||
AcSetupMsgSubject = 42,
|
||||
#[strum(props(
|
||||
fallback = "This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device."
|
||||
))]
|
||||
AcSetupMsgBody = 43,
|
||||
#[strum(props(fallback = "Messages I sent to myself"))]
|
||||
SelfTalkSubTitle = 50,
|
||||
#[strum(props(fallback = "Cannot login as %1$s."))]
|
||||
CannotLogin = 60,
|
||||
#[strum(props(fallback = "Response from %1$s: %2$s"))]
|
||||
ServerResponse = 61,
|
||||
#[strum(props(fallback = "%1$s by %2$s."))]
|
||||
MsgActionByUser = 62,
|
||||
#[strum(props(fallback = "%1$s by me."))]
|
||||
MsgActionByMe = 63,
|
||||
#[strum(props(fallback = "Location streaming enabled."))]
|
||||
MsgLocationEnabled = 64,
|
||||
#[strum(props(fallback = "Location streaming disabled."))]
|
||||
MsgLocationDisabled = 65,
|
||||
#[strum(props(fallback = "Location"))]
|
||||
Location = 66,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
/// Default untranslated strings for stock messages.
|
||||
///
|
||||
/// These could be used in logging calls, so no logging here.
|
||||
fn fallback(&self) -> &'static str {
|
||||
self.get_str("fallback").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Return the stock string for the [StockMessage].
|
||||
///
|
||||
/// If the context callback responds with a string to use, e.g. a
|
||||
/// translation, then this string will be returned. Otherwise a
|
||||
/// default (English) string is returned.
|
||||
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
|
||||
let ptr = self.call_cb(Event::GET_STRING, id as usize, 0) as *mut libc::c_char;
|
||||
if ptr.is_null() {
|
||||
Cow::Borrowed(id.fallback())
|
||||
} else {
|
||||
let ret = to_string(ptr);
|
||||
unsafe { free(ptr as *mut libc::c_void) };
|
||||
Cow::Owned(ret)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return stock string, replacing placeholders with provided string.
|
||||
///
|
||||
/// This replaces both the *first* `%1$s` **and** `%1$d`
|
||||
/// placeholders with the provided string.
|
||||
pub fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef<str>) -> String {
|
||||
self.stock_str(id)
|
||||
.replacen("%1$s", insert.as_ref(), 1)
|
||||
.replacen("%1$d", insert.as_ref(), 1)
|
||||
}
|
||||
|
||||
/// Return stock string, replacing placeholders with provided int.
|
||||
///
|
||||
/// Like [Context::stock_string_repl_str] but substitute the placeholders
|
||||
/// with an integer.
|
||||
pub fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String {
|
||||
self.stock_string_repl_str(id, format!("{}", insert).as_str())
|
||||
}
|
||||
|
||||
/// Return stock string, replacing 2 placeholders with provided string.
|
||||
///
|
||||
/// This replaces both the *first* `%1$s` **and** `%1$d`
|
||||
/// placeholders with the string in `insert` and does the same for
|
||||
/// `%2$s` and `%2$d` for `insert2`.
|
||||
fn stock_string_repl_str2(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
insert: impl AsRef<str>,
|
||||
insert2: impl AsRef<str>,
|
||||
) -> String {
|
||||
self.stock_str(id)
|
||||
.replacen("%1$s", insert.as_ref(), 1)
|
||||
.replacen("%1$d", insert.as_ref(), 1)
|
||||
.replacen("%2$s", insert2.as_ref(), 1)
|
||||
.replacen("%2$d", insert2.as_ref(), 1)
|
||||
}
|
||||
|
||||
/// Return some kind of stock message
|
||||
///
|
||||
/// If the `id` is [StockMessage::MsgAddMember] or
|
||||
/// [StockMessage::MsgDelMember] then `param1` is considered to be the
|
||||
/// contact address and will be replaced by that contact's display
|
||||
/// name.
|
||||
///
|
||||
/// If `from_id` is not `0`, any trailing dot is removed from the
|
||||
/// first stock string created so far. If the `from_id` contact is
|
||||
/// the user itself, i.e. `DC_CONTACT_ID_SELF` the string is used
|
||||
/// itself as param to the [StockMessage::MsgActionByMe] stock string
|
||||
/// resulting in a string like "Member Alice added by me." (for
|
||||
/// [StockMessage::MsgAddMember] as `id`). If the `from_id` contact
|
||||
/// is any other user than the contact's display name is looked up and
|
||||
/// used as the second parameter to [StockMessage::MsgActionByUser] with
|
||||
/// again the original stock string being used as the first parameter,
|
||||
/// resulting in a string like "Member Alice added by Bob.".
|
||||
pub fn stock_system_msg(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
param1: impl AsRef<str>,
|
||||
param2: impl AsRef<str>,
|
||||
from_id: u32,
|
||||
) -> String {
|
||||
let insert1 = if id == StockMessage::MsgAddMember || id == StockMessage::MsgDelMember {
|
||||
let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref());
|
||||
if contact_id != 0 {
|
||||
Contact::get_by_id(self, contact_id)
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
param1.as_ref().to_string()
|
||||
}
|
||||
} else {
|
||||
param1.as_ref().to_string()
|
||||
};
|
||||
|
||||
let action = self.stock_string_repl_str2(id, insert1, param2.as_ref().to_string());
|
||||
let action1 = action.trim_end_matches('.');
|
||||
match from_id {
|
||||
0 => action,
|
||||
1 => self.stock_string_repl_str(StockMessage::MsgActionByMe, action1), // DC_CONTACT_ID_SELF
|
||||
_ => {
|
||||
let displayname = Contact::get_by_id(self, from_id)
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default();
|
||||
|
||||
self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
use crate::context::dc_context_new;
|
||||
use crate::types::uintptr_t;
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
#[test]
|
||||
fn test_enum_mapping() {
|
||||
assert_eq!(StockMessage::NoMessages.to_usize().unwrap(), 1);
|
||||
assert_eq!(StockMessage::SelfMsg.to_usize().unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fallback() {
|
||||
assert_eq!(StockMessage::NoMessages.fallback(), "No messages.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
}
|
||||
|
||||
unsafe extern "C" fn test_stock_str_no_fallback_cb(
|
||||
_ctx: &Context,
|
||||
evt: Event,
|
||||
d1: uintptr_t,
|
||||
_d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() {
|
||||
let tmp = CString::new("Hello there").unwrap();
|
||||
dc_strdup(tmp.as_ptr()) as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str_no_fallback() {
|
||||
let t = test_context(Some(test_stock_str_no_fallback_cb));
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
// uses %1$s substitution
|
||||
assert_eq!(
|
||||
ctx.stock_string_repl_str(StockMessage::Member, "42"),
|
||||
"42 member(s)"
|
||||
);
|
||||
// We have no string using %1$d to test...
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_int() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
ctx.stock_string_repl_int(StockMessage::Member, 42),
|
||||
"42 member(s)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str2() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
|
||||
"Response from foo: bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_simple() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
|
||||
"Location streaming enabled."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_me() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
"Member alice@example.com added by me."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_me_with_displayname() {
|
||||
let t = dummy_context();
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com").expect("failed to create contact");
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
"Member Alice (alice@example.com) added by me."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_other_with_displayname() {
|
||||
let t = dummy_context();
|
||||
let contact_id = {
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.expect("Failed to create contact Alice");
|
||||
let id =
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
|
||||
id
|
||||
};
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
contact_id,
|
||||
),
|
||||
"Member Alice (alice@example.com) added by Bob (bob@example.com)."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_grp_name() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
"Some chat",
|
||||
"Other chat",
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
"Group name changed from \"Some chat\" to \"Other chat\" by me."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_grp_name_other() {
|
||||
let t = dummy_context();
|
||||
let id = Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.expect("failed to create contact");
|
||||
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id,),
|
||||
"Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)."
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
//! Utilities to help writing tests.
|
||||
//!
|
||||
//! 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.
|
||||
///
|
||||
/// The temporary directory can be used to store the SQLite database,
|
||||
/// see e.g. [test_context] which does this.
|
||||
pub struct TestContext {
|
||||
pub ctx: Context,
|
||||
pub dir: TempDir,
|
||||
}
|
||||
|
||||
/// Create a new, opened [TestContext] using given callback.
|
||||
///
|
||||
/// The [Context] will be opened with the SQLite database named
|
||||
/// "db.sqlite" in the [TestContext.dir] directory.
|
||||
///
|
||||
/// [Context]: crate::context::Context
|
||||
pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
|
||||
unsafe {
|
||||
let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
assert!(
|
||||
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
|
||||
"Failed to open {}",
|
||||
dbfile.display(),
|
||||
);
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a dummy [TestContext].
|
||||
///
|
||||
/// The context will be opened and use the SQLite database as
|
||||
/// specified in [test_context] but there is no callback hooked up,
|
||||
/// i.e. [Context::call_cb] will always return `0`.
|
||||
pub fn dummy_context() -> TestContext {
|
||||
test_context(None)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user