Compare commits

..

3 Commits

Author SHA1 Message Date
dignifiedquire
fc4ea0b7ae rental example 2019-08-26 23:40:01 +02:00
Floris Bruynooghe
651ad60f18 Put the context in an RwLock 2019-08-26 22:23:33 +02:00
Floris Bruynooghe
b2031f5bcd Remove dc_open() from the Rust API
This means the Context becomes a struct following the normal Rust
conventions where when it is created it is usable.  This will over
time allow removing a lot of runtime checks and simplify code.  Many
members will no longer need to be Options or similar.

The C API needs to remain compatible so hides the implementation of
this behind another struct which can be opened and closed.
2019-08-25 20:41:47 +02:00
52 changed files with 5595 additions and 9242 deletions

View File

@@ -1,4 +1,5 @@
version: 2.1
executors:
default:
docker:
@@ -152,12 +153,11 @@ 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

4
.gitignore vendored
View File

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

9
Cargo.lock generated
View File

@@ -463,7 +463,6 @@ version = "1.0.0-alpha.4"
dependencies = [
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -479,7 +478,7 @@ dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"mmime 0.1.2-alpha.0 (git+https://github.com/dignifiedquire/mmime?rev=bccd2c2)",
"mmime 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1192,8 +1191,8 @@ dependencies = [
[[package]]
name = "mmime"
version = "0.1.2-alpha.0"
source = "git+https://github.com/dignifiedquire/mmime?rev=bccd2c2#bccd2c2c89e9241e05f321c963f638affdccad96"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2884,7 +2883,7 @@ dependencies = [
"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10"
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum mmime 0.1.2-alpha.0 (git+https://github.com/dignifiedquire/mmime?rev=bccd2c2)" = "<none>"
"checksum mmime 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a1246fa340840c36f1fca1507db82463fbc4c2f7763fe84bfde666c7381e0593"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b"

View File

@@ -24,7 +24,7 @@ num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = "0.9.0"
imap = "1.0.1"
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
mmime = "0.1.0"
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
@@ -49,7 +49,6 @@ 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"

View File

@@ -34,7 +34,7 @@ echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip3 install devpi-client
pip install devpi-client
devpi use https://m.devpi.net
devpi login dc --password $DEVPI_LOGIN

View File

@@ -41,7 +41,7 @@ if [ -n "$TESTS" ]; then
# see https://github.com/deltachat/deltachat-core-rust/issues/331
# unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e lint,py35,py36,py37,auditwheels
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
popd
fi

View File

@@ -19,6 +19,7 @@ deltachat = { path = "../", default-features = false }
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
rental = "0.5.4"
[features]
default = ["vendored", "nightly", "ringbuf"]

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
use std::ffi::CString;
use std::ptr;
use std::str::FromStr;
use deltachat::chat::{self, Chat};
@@ -10,11 +9,11 @@ use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_location::*;
use deltachat::dc_receive_imf::*;
use deltachat::dc_tools::*;
use deltachat::error::Error;
use deltachat::job::*;
use deltachat::location;
use deltachat::lot::LotState;
use deltachat::message::*;
use deltachat::peerstate::*;
@@ -95,7 +94,7 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) -> libc::c_int {
/* mainly for testing, may be called by dc_import_spec() */
let mut success: libc::c_int = 0i32;
let mut data: *mut libc::c_char = ptr::null_mut();
let mut data: *mut libc::c_char = 0 as *mut libc::c_char;
let mut data_bytes: size_t = 0;
if !(dc_read_file(
context,
@@ -129,7 +128,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
let ok_to_continue;
let mut success: libc::c_int = 0;
let real_spec: *mut libc::c_char;
let mut suffix: *mut libc::c_char = ptr::null_mut();
let mut suffix: *mut libc::c_char = 0 as *mut libc::c_char;
let mut read_cnt: libc::c_int = 0;
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
@@ -397,8 +396,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
_ => println!(
"==========================Database commands==\n\
info\n\
open <file to open or create>\n\
close\n\
set <configuration-key> [<value>]\n\
get <configuration-key>\n\
oauth2\n\
@@ -475,14 +472,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("Already authorized.");
}
}
"open" => {
ensure!(!arg1.is_empty(), "Argument <file> missing");
dc_close(context);
ensure!(dc_open(context, arg1, None), "Open failed");
}
"close" => {
dc_close(context);
}
"initiate-key-transfer" => {
let setup_code = dc_initiate_key_transfer(context);
if !setup_code.is_null() {
@@ -527,17 +516,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
}
"export-backup" => {
dc_imex(context, 11, context.get_blobdir(), ptr::null());
dc_imex(context, 11, context.get_blobdir(), 0 as *const libc::c_char);
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, arg1_c, ptr::null());
dc_imex(context, 12, arg1_c, 0 as *const libc::c_char);
}
"export-keys" => {
dc_imex(context, 1, context.get_blobdir(), ptr::null());
dc_imex(context, 1, context.get_blobdir(), 0 as *const libc::c_char);
}
"import-keys" => {
dc_imex(context, 2, context.get_blobdir(), ptr::null());
dc_imex(context, 2, context.get_blobdir(), 0 as *const libc::c_char);
}
"export-setup" => {
let setup_code = dc_create_setup_code(context);
@@ -653,7 +642,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
}
}
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
if dc_is_sending_locations_to_chat(context, 0 as uint32_t) {
info!(context, 0, "Location streaming enabled.");
}
println!("{} chats", cnt);
@@ -779,17 +768,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!(
"{} contacts\nLocation streaming: {}",
contacts.len(),
location::is_sending_locations_to_chat(
context,
sel_chat.as_ref().unwrap().get_id()
),
dc_is_sending_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id()),
);
}
"getlocations" => {
ensure!(sel_chat.is_some(), "No chat selected.");
let contact_id = arg1.parse().unwrap_or_default();
let locations = location::get_range(
let locations = dc_get_locations(
context,
sel_chat.as_ref().unwrap().get_id(),
contact_id,
@@ -823,7 +809,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!arg1.is_empty(), "No timeout given.");
let seconds = arg1.parse()?;
location::send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds);
dc_send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds);
println!(
"Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.",
sel_chat.as_ref().unwrap().get_id(),
@@ -838,7 +824,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let latitude = arg1.parse()?;
let longitude = arg2.parse()?;
let continue_streaming = location::set(context, latitude, longitude, 0.);
let continue_streaming = dc_set_location(context, latitude, longitude, 0.);
if 0 != continue_streaming {
println!("Success, streaming should be continued.");
} else {
@@ -846,7 +832,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
}
"dellocations" => {
location::delete_all(context)?;
dc_delete_all_locations(context);
}
"send" => {
ensure!(sel_chat.is_some(), "No chat selected.");
@@ -872,7 +858,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
Viewtype::File
},
);
dc_msg_set_file(&mut msg, arg1_c, ptr::null());
dc_msg_set_file(&mut msg, arg1_c, 0 as *const libc::c_char);
dc_msg_set_text(&mut msg, arg2_c);
chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?;
}

View File

@@ -14,7 +14,7 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::ptr;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
@@ -42,7 +42,7 @@ use self::cmdline::*;
// Event Handler
unsafe extern "C" fn receive_event(
fn receive_event(
_context: &Context,
event: Event,
data1: uintptr_t,
@@ -385,17 +385,16 @@ 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()));
unsafe { dc_cmdline_skip_auth() };
if args.len() == 2 {
if unsafe { !dc_open(&mut context, &args[1], None) } {
println!("Error: Cannot open {}.", args[0],);
}
} else if args.len() != 1 {
if args.len() != 2 {
println!("Error: Bad arguments, expected [db-name].");
return Err(format_err!("No db-name specified"));
}
let context = Context::new(
Box::new(receive_event),
"CLI".into(),
Path::new(&args[1]).to_path_buf(),
)?;
println!("Delta Chat Core is awaiting your commands.");

View File

@@ -16,9 +16,8 @@ use deltachat::job::{
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
};
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
match event {
Event::CONFIGURE_PROGRESS => {
println!(" progress: {}", data1);
@@ -39,7 +38,11 @@ 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 dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("creating database {:?}", dbfile);
let ctx =
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
let running = Arc::new(RwLock::new(true));
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
@@ -73,13 +76,6 @@ fn main() {
}
});
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("opening database {:?}", dbfile);
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
println!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");

View File

@@ -17,11 +17,19 @@ def test_callback_None2int():
clear_context_callback(ctx)
def test_dc_close_events():
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
def test_dc_close_events(tmpdir):
ctx = ffi.gc(
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
evlog = EventLogger(ctx)
evlog.set_timeout(5)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
set_context_callback(
ctx,
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2),
)
p = tmpdir.join("hello.db")
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
capi.lib.dc_close(ctx)
def find(info_string):

View File

@@ -1,6 +1,7 @@
[tox]
# make sure to update environment list in travis.yml and appveyor.yml
envlist =
py27
py35
lint
auditwheels

View File

@@ -303,8 +303,11 @@ impl<'a> Chat<'a> {
"Cannot send message; self not in group.",
);
} else {
if let Some(from) = context.sql.get_config(context, "configured_addr") {
let from_c = CString::yolo(from);
let from = context.sql.get_config(context, "configured_addr");
if from.is_none() {
error!(context, 0, "Cannot send message, not configured.",);
} else {
let from_c = CString::yolo(from.unwrap());
new_rfc724_mid = dc_create_outgoing_rfc724_mid(
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
self.grpid.strdup()
@@ -468,7 +471,7 @@ impl<'a> Chat<'a> {
VALUES (?,?,?, ?,?,1);",
params![
timestamp,
DC_CONTACT_ID_SELF,
DC_CONTACT_ID_SELF as i32,
self.id as i32,
msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
@@ -526,8 +529,6 @@ impl<'a> Chat<'a> {
);
}
}
} else {
error!(context, 0, "Cannot send message, not configured.",);
}
}
@@ -566,7 +567,7 @@ pub fn create_by_msg_id(context: &Context, msg_id: u32) -> Result<u32, Error> {
if let Ok(msg) = dc_msg_load_from_db(context, msg_id) {
if let Ok(chat) = Chat::load_from_db(context, msg.chat_id) {
if chat.id > DC_CHAT_ID_LAST_SPECIAL {
if chat.id > DC_CHAT_ID_LAST_SPECIAL as u32 {
chat_id = chat.id;
if chat.blocked != Blocked::Not {
unblock(context, chat.id);
@@ -602,7 +603,8 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result<u32, E
chat_id
}
Err(err) => {
if !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF
if !Contact::real_exists_by_id(context, contact_id)
&& contact_id != DC_CONTACT_ID_SELF as u32
{
warn!(
context,
@@ -705,7 +707,7 @@ pub fn prepare_msg<'a>(
msg: &mut Message<'a>,
) -> Result<u32, Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
"Cannot prepare message for special chat"
);
@@ -883,7 +885,7 @@ pub unsafe fn send_msg<'a>(
);
if msg.param.exists(Param::SetLatitude) {
context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF as usize, 0);
context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF, 0);
}
if 0 == chat_id {
@@ -915,7 +917,7 @@ pub unsafe fn send_text_msg(
text_to_send: String,
) -> Result<u32, Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
"bad chat_id = {} <= 9",
chat_id
);
@@ -927,7 +929,7 @@ pub unsafe fn send_text_msg(
// passing `None` as message jsut deletes the draft
pub unsafe fn set_draft(context: &Context, chat_id: u32, msg: Option<&mut Message>) {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
return;
}
if set_draft_raw(context, chat_id, msg) {
@@ -1005,7 +1007,7 @@ fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 {
}
pub unsafe fn get_draft(context: &Context, chat_id: u32) -> Result<Message, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID");
let draft_msg_id = get_draft_msg_id(context, chat_id);
ensure!(draft_msg_id != 0, "Invalid draft message ID");
@@ -1023,13 +1025,13 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
for row in rows {
let (curr_id, ts) = row?;
if curr_id as u32 == marker1before {
ret.push(DC_MSG_ID_MARKER1);
ret.push(DC_MSG_ID_MARKER1 as u32);
}
if 0 != flags & 0x1 {
let curr_local_timestamp = ts + cnv_to_local;
let curr_day = (curr_local_timestamp / 86400) as libc::c_int;
if curr_day != last_day {
ret.push(DC_MSG_ID_LAST_SPECIAL);
ret.push(DC_MSG_ID_LAST_SPECIAL as u32);
last_day = curr_day;
}
}
@@ -1232,7 +1234,7 @@ pub unsafe fn get_next_media(
pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
"bad chat_id = {} <= 9",
chat_id
);
@@ -1264,7 +1266,7 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err
pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
"bad chat_id = {} <= 9",
chat_id
);
@@ -1404,7 +1406,7 @@ pub unsafe fn add_contact_to_chat_ex(
let mut success: libc::c_int = 0;
let contact = Contact::get_by_id(context, contact_id);
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
return 0;
}
let mut msg = dc_msg_new_untyped(context);
@@ -1413,88 +1415,90 @@ pub unsafe fn add_contact_to_chat_ex(
let contact = contact.unwrap();
/*this also makes sure, not contacts are added to special or normal chats*/
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
if !(!real_group_exists(context, chat_id)
|| !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF)
{
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
log_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot add contact to group; self not in group.",
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if 0 != flags & 0x1
&& chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
{
chat.param.remove(Param::Unpromoted);
chat.update_param().unwrap();
}
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if contact.get_addr() != &self_addr {
// ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly.
// if SELF is not in the group, members cannot be added at all.
let chat = Chat::load_from_db(context, chat_id);
if 0 != is_contact_in_chat(context, chat_id, contact_id) {
if 0 == flags & 0x1 {
success = 1;
OK_TO_CONTINUE = false;
}
} else {
// else continue and send status mail
if chat.typ == Chattype::VerifiedGroup {
if contact.is_verified() != VerifiedStatus::BidirectVerified {
error!(
if !(!real_group_exists(context, chat_id)
|| !Contact::real_exists_by_id(context, contact_id)
&& contact_id != DC_CONTACT_ID_SELF as u32
|| chat.is_err())
{
let mut chat = chat.unwrap();
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
log_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot add contact to group; self not in group.",
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if 0 != flags & 0x1 && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
chat.param.remove(Param::Unpromoted);
chat.update_param().unwrap();
}
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if contact.get_addr() != &self_addr {
// ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly.
// if SELF is not in the group, members cannot be added at all.
if 0 != is_contact_in_chat(context, chat_id, contact_id) {
if 0 == flags & 0x1 {
success = 1;
OK_TO_CONTINUE = false;
}
} else {
// else continue and send status mail
if chat.typ == Chattype::VerifiedGroup {
if contact.is_verified() != VerifiedStatus::BidirectVerified {
error!(
context, 0,
"Only bidirectional verified contacts can be added to verified groups."
);
OK_TO_CONTINUE = false;
}
}
if OK_TO_CONTINUE {
if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) {
OK_TO_CONTINUE = false;
}
OK_TO_CONTINUE = false;
}
}
if OK_TO_CONTINUE {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgAddMember,
contact.get_addr(),
"",
DC_CONTACT_ID_SELF as u32,
));
msg.param.set_int(Param::Cmd, 4);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, flags);
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(
Event::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) {
OK_TO_CONTINUE = false;
}
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t);
success = 1;
}
}
if OK_TO_CONTINUE {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
StockMessage::MsgAddMember,
contact.get_addr(),
"",
DC_CONTACT_ID_SELF as u32,
));
msg.param.set_int(Param::Cmd, 4);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, flags);
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(
Event::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
}
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t);
success = 1;
}
}
}
};
}
success
}
fn real_group_exists(context: &Context, chat_id: u32) -> bool {
// check if a group or a verified group exists under the given ID
if !context.sql.is_open() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if !context.sql.is_open() || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
return false;
}
@@ -1547,68 +1551,72 @@ pub unsafe fn remove_contact_from_chat(
contact_id: u32,
) -> Result<(), Error> {
ensure!(
chat_id > DC_CHAT_ID_LAST_SPECIAL,
chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
"bad chat_id = {} <= 9",
chat_id
);
ensure!(contact_id != DC_CONTACT_ID_SELF, "Cannot remove self");
ensure!(
contact_id != DC_CONTACT_ID_SELF as u32,
"Cannot remove self"
);
let mut msg = dc_msg_new_untyped(context);
let mut success = false;
/* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
if let Ok(chat) = Chat::load_from_db(context, chat_id) {
if real_group_exists(context, chat_id) {
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
log_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot remove contact from chat; self not in group.",
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
if contact.id == DC_CONTACT_ID_SELF {
set_group_explicitly_left(context, chat.grpid).unwrap();
msg.text = Some(context.stock_system_msg(
StockMessage::MsgGroupLeft,
"",
"",
DC_CONTACT_ID_SELF,
));
} else {
msg.text = Some(context.stock_system_msg(
StockMessage::MsgDelMember,
contact.get_addr(),
"",
DC_CONTACT_ID_SELF,
));
}
msg.param.set_int(Param::Cmd, 5);
msg.param.set(Param::Arg, contact.get_addr());
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(
Event::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
let chat = Chat::load_from_db(context, chat_id);
if !(!real_group_exists(context, chat_id) || chat.is_err()) {
let chat = chat.unwrap();
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
log_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot remove contact from chat; self not in group.",
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
msg.type_0 = Viewtype::Text;
if contact.id == DC_CONTACT_ID_SELF as u32 {
set_group_explicitly_left(context, chat.grpid).unwrap();
msg.text = Some(context.stock_system_msg(
StockMessage::MsgGroupLeft,
"",
"",
DC_CONTACT_ID_SELF as u32,
));
} else {
msg.text = Some(context.stock_system_msg(
StockMessage::MsgDelMember,
contact.get_addr(),
"",
DC_CONTACT_ID_SELF as u32,
));
}
msg.param.set_int(Param::Cmd, 5);
msg.param.set(Param::Arg, contact.get_addr());
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(
Event::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
}
if sql::execute(
context,
&context.sql,
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;",
params![chat_id as i32, contact_id as i32],
)
.is_ok()
{
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t);
success = true;
}
}
if sql::execute(
context,
&context.sql,
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;",
params![chat_id as i32, contact_id as i32],
)
.is_ok()
{
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t);
success = true;
}
}
}
@@ -1649,7 +1657,7 @@ pub unsafe fn set_chat_name(
let mut success = false;
ensure!(!new_name.as_ref().is_empty(), "Invalid name");
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID");
let chat = Chat::load_from_db(context, chat_id)?;
let mut msg = dc_msg_new_untyped(context);
@@ -1684,7 +1692,7 @@ pub unsafe fn set_chat_name(
StockMessage::MsgGrpName,
&chat.name,
new_name.as_ref(),
DC_CONTACT_ID_SELF,
DC_CONTACT_ID_SELF as u32,
));
msg.param.set_int(Param::Cmd, 2);
if !chat.name.is_empty() {
@@ -1720,7 +1728,7 @@ pub unsafe fn set_chat_profile_image(
chat_id: u32,
new_image: impl AsRef<str>,
) -> Result<(), Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID");
let mut OK_TO_CONTINUE = true;
let mut success = false;
@@ -1768,7 +1776,7 @@ pub unsafe fn set_chat_profile_image(
},
"",
"",
DC_CONTACT_ID_SELF,
DC_CONTACT_ID_SELF as u32,
));
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
context.call_cb(
@@ -1800,7 +1808,7 @@ pub unsafe fn forward_msgs(
msg_cnt: libc::c_int,
chat_id: u32,
) {
if msg_ids.is_null() || msg_cnt <= 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if msg_ids.is_null() || msg_cnt <= 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
return;
}
@@ -1839,7 +1847,7 @@ pub unsafe fn forward_msgs(
}
let mut msg = msg.unwrap();
let original_param = msg.param.clone();
if msg.from_id != DC_CONTACT_ID_SELF {
if msg.from_id != DC_CONTACT_ID_SELF as u32 {
msg.param.set_int(Param::Forwarded, 1);
}
msg.param.remove(Param::GuranteeE2ee);

View File

@@ -195,9 +195,9 @@ impl<'a> Chatlist<'a> {
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
ids.push((DC_CHAT_ID_ALLDONE_HINT as u32, 0));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
ids.push((DC_CHAT_ID_ARCHIVED_LINK as u32, 0));
}
Ok(Chatlist { context, ids })
@@ -247,7 +247,7 @@ impl<'a> Chatlist<'a> {
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
pub fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> Lot {
pub unsafe fn get_summary(&self, index: usize, 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".
@@ -290,9 +290,10 @@ impl<'a> Chatlist<'a> {
None
};
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
if chat.id == DC_CHAT_ID_ARCHIVED_LINK as u32 {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
} else if lastmsg.is_none()
|| lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED as u32
{
ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string());
} else {

View File

@@ -33,7 +33,6 @@ pub enum Config {
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,

View File

@@ -126,7 +126,7 @@ pub unsafe fn outlk_autodiscover(
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();
(*outlk_ad).config[i] = 0 as *mut libc::c_char;
}
}

View File

@@ -2,9 +2,9 @@ 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::e2ee;
use crate::imap::*;
use crate::job::*;
use crate::oauth2::*;
@@ -149,9 +149,12 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
ok_to_continue0 = true;
}
if ok_to_continue0 {
let parsed: Result<EmailAddress, _> = param.addr.parse();
let mut ok_to_continue7 = false;
if let Ok(parsed) = param.addr.parse() {
let parsed: EmailAddress = parsed;
if parsed.is_err() {
error!(context, 0, "Bad email-address.");
} else {
let parsed = parsed.unwrap();
let param_domain = parsed.domain;
let param_addr_urlencoded =
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
@@ -589,7 +592,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
.ok();
if !s.shall_stop_ongoing {
progress!(context, 920);
e2ee::ensure_secret_key_exists(context);
dc_ensure_secret_key_exists(context);
success = true;
info!(
context,
@@ -606,8 +609,6 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
}
}
}
} else {
error!(context, 0, "Bad email-address.");
}
}
}

View File

@@ -65,19 +65,19 @@ const DC_IMEX_EXPORT_BACKUP: usize = 11;
const DC_IMEX_IMPORT_BACKUP: usize = 12;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
pub(crate) 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;
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;
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,
@@ -106,19 +106,19 @@ impl Default for Chattype {
}
}
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_MSG_ID_MARKER1: usize = 1;
const DC_MSG_ID_DAYMARKER: usize = 9;
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
/// approx. max. length returned by dc_msg_get_text()
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_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CONTACT_ID_UNDEFINED: usize = 0;
pub const DC_CONTACT_ID_SELF: usize = 1;
const DC_CONTACT_ID_DEVICE: usize = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
pub const DC_CREATE_MVBOX: usize = 1;

View File

@@ -7,9 +7,9 @@ use crate::aheader::EncryptPreference;
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_e2ee::*;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::key::*;
use crate::message::MessageState;
@@ -152,7 +152,7 @@ pub enum VerifiedStatus {
impl<'a> Contact<'a> {
pub fn load_from_db(context: &'a Context, contact_id: u32) -> Result<Self> {
if contact_id == DC_CONTACT_ID_SELF {
if contact_id == DC_CONTACT_ID_SELF as u32 {
let contact = Contact {
context,
id: contact_id,
@@ -545,7 +545,7 @@ impl<'a> Contact<'a> {
}
if 0 != listflags & DC_GCL_ADD_SELF as u32 && add_self {
ret.push(DC_CONTACT_ID_SELF);
ret.push(DC_CONTACT_ID_SELF as u32);
}
Ok(ret)
@@ -603,7 +603,7 @@ impl<'a> Contact<'a> {
});
ret += &p;
if self_key.is_none() {
e2ee::ensure_secret_key_exists(context)?;
dc_ensure_secret_key_exists(context)?;
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
}
let p = context.stock_str(StockMessage::FingerPrints);
@@ -655,7 +655,7 @@ impl<'a> Contact<'a> {
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
pub fn delete(context: &Context, contact_id: u32) -> Result<()> {
ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
contact_id > DC_CONTACT_ID_LAST_SPECIAL as u32,
"Can not delete special contact"
);
@@ -780,7 +780,7 @@ impl<'a> Contact<'a> {
/// This is the image set by each remote user on their own
/// using dc_set_config(context, "selfavatar", image).
pub fn get_profile_image(&self) -> Option<String> {
if self.id == DC_CONTACT_ID_SELF {
if self.id == DC_CONTACT_ID_SELF as u32 {
return self.context.get_config(Config::Selfavatar);
}
// TODO: else get image_abs from contact param
@@ -810,7 +810,7 @@ impl<'a> Contact<'a> {
pub fn is_verified_ex(&self, peerstate: Option<&Peerstate<'a>>) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
if self.id == DC_CONTACT_ID_SELF {
if self.id == DC_CONTACT_ID_SELF as u32 {
return VerifiedStatus::BidirectVerified;
}

View File

@@ -1,11 +1,15 @@
use std::ffi::OsString;
use std::path::Path;
use std::sync::{Arc, Condvar, Mutex, RwLock};
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_loginparam::*;
use crate::dc_move::*;
use crate::dc_receive_imf::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::imap::*;
use crate::job::*;
use crate::job_thread::JobThread;
@@ -21,6 +25,15 @@ use std::ptr;
use std::path::PathBuf;
/// Callback function type for [Context].
///
/// @param context The context object as returned by dc_context_new().
/// @param event one of the @ref DC_EVENT constants
/// @param data1 depends on the event parameter
/// @param data2 depends on the event parameter
/// @return return 0 unless stated otherwise in the event parameter documentation
pub type ContextCallback = dyn Fn(&Context, Event, uintptr_t, uintptr_t) -> uintptr_t;
pub struct Context {
pub userdata: *mut libc::c_void,
pub dbfile: Arc<RwLock<Option<PathBuf>>>,
@@ -34,7 +47,7 @@ pub struct Context {
pub smtp: Arc<Mutex<Smtp>>,
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
pub oauth2_critical: Arc<Mutex<()>>,
pub cb: Option<dc_callback_t>,
pub cb: Box<ContextCallback>,
pub os_name: Option<String>,
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
pub bob: Arc<RwLock<BobStatus>>,
@@ -54,6 +67,76 @@ pub struct RunningState {
}
impl Context {
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default());
blob_fname.push("-blobs");
let blobdir = dbfile.with_file_name(blob_fname);
if !blobdir.exists() {
std::fs::create_dir_all(&blobdir)?;
}
Context::with_blobdir(cb, os_name, dbfile, &blobdir)
}
pub fn with_blobdir(
cb: Box<ContextCallback>,
os_name: String,
dbfile: PathBuf,
blobdir: &Path,
) -> Result<Context> {
if !blobdir.is_dir() {
return Err(format_err!("Blobdir does not exist: {}", blobdir.display()));
}
let ctx = Context {
blobdir: Arc::new(RwLock::new(unsafe { blobdir.strdup() })),
dbfile: Arc::new(RwLock::new(Some(dbfile))),
inbox: Arc::new(RwLock::new({
Imap::new(
cb_get_config,
cb_set_config,
cb_precheck_imf,
cb_receive_imf,
)
})),
userdata: std::ptr::null_mut(),
cb,
os_name: Some(os_name),
running_state: Arc::new(RwLock::new(Default::default())),
sql: Sql::new(),
smtp: Arc::new(Mutex::new(Smtp::new())),
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
oauth2_critical: Arc::new(Mutex::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",
"configured_sentbox_folder",
Imap::new(
cb_get_config,
cb_set_config,
cb_precheck_imf,
cb_receive_imf,
),
))),
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
"MVBOX",
"configured_mvbox_folder",
Imap::new(
cb_get_config,
cb_set_config,
cb_precheck_imf,
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(()),
};
ctx.sql.open(&ctx, &ctx.dbfile.read().unwrap().as_ref().unwrap(), 0)?;
Ok(ctx)
}
pub fn has_dbfile(&self) -> bool {
self.get_dbfile().is_some()
}
@@ -73,11 +156,7 @@ impl Context {
}
pub fn call_cb(&self, event: Event, data1: uintptr_t, data2: uintptr_t) -> uintptr_t {
if let Some(cb) = self.cb {
unsafe { cb(self, event, data1, data2) }
} else {
0
}
(*self.cb)(self, event, data1, data2)
}
}
@@ -114,60 +193,6 @@ pub struct SmtpState {
pub probe_network: bool,
}
// create/open/config/information
pub fn dc_context_new(
cb: Option<dc_callback_t>,
userdata: *mut libc::c_void,
os_name: Option<String>,
) -> Context {
Context {
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
dbfile: Arc::new(RwLock::new(None)),
inbox: Arc::new(RwLock::new({
Imap::new(
cb_get_config,
cb_set_config,
cb_precheck_imf,
cb_receive_imf,
)
})),
userdata,
cb,
os_name,
running_state: Arc::new(RwLock::new(Default::default())),
sql: Sql::new(),
smtp: Arc::new(Mutex::new(Smtp::new())),
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
oauth2_critical: Arc::new(Mutex::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",
"configured_sentbox_folder",
Imap::new(
cb_get_config,
cb_set_config,
cb_precheck_imf,
cb_receive_imf,
),
))),
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
"MVBOX",
"configured_mvbox_folder",
Imap::new(
cb_get_config,
cb_set_config,
cb_precheck_imf,
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(()),
}
}
unsafe fn cb_receive_imf(
context: &Context,
imf_raw_not_terminated: *const libc::c_char,
@@ -227,7 +252,7 @@ unsafe fn cb_precheck_imf(
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
do_heuristics_moves(context, server_folder, msg_id);
dc_do_heuristics_moves(context, server_folder, msg_id);
if 0 != mark_seen {
job_add(
context,
@@ -286,41 +311,10 @@ pub unsafe fn dc_close(context: &Context) {
*blobdir = ptr::null_mut();
}
pub unsafe fn dc_is_open(context: &Context) -> libc::c_int {
context.sql.is_open() as libc::c_int
}
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;
if 0 != dc_is_open(context) {
return false;
}
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
let blobdir = blobdir.unwrap_or_default();
if !blobdir.is_empty() {
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
*context.blobdir.write().unwrap() = dir;
} else {
let dir = dbfile.to_string() + "-blobs";
dc_create_folder(context, &dir);
*context.blobdir.write().unwrap() = dir.strdup();
}
// 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 {
dc_close(context);
}
success
}
pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
dc_strdup(*context.blobdir.clone().read().unwrap())
}
@@ -574,62 +568,74 @@ pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
}
}
pub fn do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
if context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
{
return;
}
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
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);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_wrong_db() {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
std::fs::write(&dbfile, b"123").unwrap();
let res = Context::new(Box::new(|_, _, _, _| 0), "FakeOs".into(), dbfile);
assert!(res.is_err());
}
#[test]
fn test_blobdir_exists() {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile).unwrap();
let blobdir = tmp.path().join("db.sqlite-blobs");
assert!(blobdir.is_dir());
}
#[test]
fn test_wrong_blogdir() {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("db.sqlite-blobs");
std::fs::write(&blobdir, b"123").unwrap();
let res = Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile);
assert!(res.is_err());
}
#[test]
fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap();
let subdir = tmp.path().join("subdir");
let dbfile = subdir.join("db.sqlite");
let dbfile2 = dbfile.clone();
Context::new(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile).unwrap();
assert!(subdir.is_dir());
assert!(dbfile2.is_file());
}
#[test]
fn test_with_blobdir_not_exists() {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("blobs");
let res =
Context::with_blobdir(Box::new(|_, _, _, _| 0), "FakeOS".into(), dbfile, &blobdir);
assert!(res.is_err());
}
#[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);
let t = dummy_context();
std::mem::drop(t.ctx);
}
#[test]
fn test_context_double_close() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
unsafe {
dc_close(&ctx);
dc_close(&ctx);
dc_close(&t.ctx);
dc_close(&t.ctx);
}
std::mem::drop(ctx);
std::mem::drop(t.ctx);
}
}

View File

@@ -1,11 +1,11 @@
use crate::location::Location;
use crate::dc_location::dc_location;
use crate::types::*;
/* * the structure behind dc_array_t */
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub enum dc_array_t {
Locations(Vec<Location>),
Locations(Vec<dc_location>),
Uint(Vec<u32>),
}
@@ -27,7 +27,7 @@ impl dc_array_t {
}
}
pub fn add_location(&mut self, location: Location) {
pub fn add_location(&mut self, location: dc_location) {
if let Self::Locations(array) = self {
array.push(location)
} else {
@@ -42,7 +42,7 @@ impl dc_array_t {
}
}
pub fn get_location(&self, index: usize) -> &Location {
pub fn get_location(&self, index: usize) -> &dc_location {
if let Self::Locations(array) = self {
&array[index]
} else {
@@ -108,8 +108,8 @@ impl From<Vec<u32>> for dc_array_t {
}
}
impl From<Vec<Location>> for dc_array_t {
fn from(array: Vec<Location>) -> Self {
impl From<Vec<dc_location>> for dc_array_t {
fn from(array: Vec<dc_location>) -> Self {
dc_array_t::Locations(array)
}
}

View File

@@ -2,6 +2,9 @@ use lazy_static::lazy_static;
use quick_xml;
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::dc_tools::*;
use crate::x::*;
lazy_static! {
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
}
@@ -21,20 +24,19 @@ enum AddText {
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
// the lineends are typically remove in further processing by the caller
pub fn dc_dehtml(buf_terminated: &str) -> String {
let buf_terminated = buf_terminated.trim();
if buf_terminated.is_empty() {
return "".into();
pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
dc_trim(buf_terminated);
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(buf_terminated.len()),
strbuilder: String::with_capacity(strlen(buf_terminated)),
add_text: AddText::YesRemoveLineEnds,
last_href: None,
};
let mut reader = quick_xml::Reader::from_str(buf_terminated);
let mut reader = quick_xml::Reader::from_str(as_str(buf_terminated));
let mut buf = Vec::new();
@@ -59,7 +61,7 @@ pub fn dc_dehtml(buf_terminated: &str) -> String {
buf.clear();
}
dehtml.strbuilder
dehtml.strbuilder.strdup()
}
fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ use crate::config::Config;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_e2ee::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::job::*;
use crate::key::*;
@@ -74,7 +74,7 @@ pub unsafe fn dc_imex_has_backup(
let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
if sql.open(context, &path, 0x1) {
if sql.open(context, &path, 0x1).is_ok() {
let curr_backup_time =
sql.get_config_int(context, "backup_time")
.unwrap_or_default() as u64;
@@ -196,7 +196,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
passphrase.len() >= 2,
"Passphrase must be at least 2 chars long."
);
let self_addr = e2ee::ensure_secret_key_exists(context)?;
let self_addr = dc_ensure_secret_key_exists(context)?;
let private_key = Key::from_self_private(context, self_addr, &context.sql)
.ok_or(format_err!("Failed to get private key."))?;
let ac_headers = match context
@@ -522,7 +522,7 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
} else {
if what == 1 || what == 11 {
/* before we export anything, make sure the private key exists */
if e2ee::ensure_secret_key_exists(context).is_err() {
if dc_ensure_secret_key_exists(context).is_err() {
error!(
context,
0,
@@ -617,9 +617,10 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
}
/* error already logged */
/* re-open copied database file */
if !context
if context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0)
.is_err()
{
return 0;
}
@@ -710,9 +711,9 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
The macro avoids weird values of 0% or 100% while still working. */
// TODO should return bool /rtn
#[allow(non_snake_case)]
#[allow(non_snake_case, unused_must_use)]
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
let mut ok_to_continue: bool;
let mut current_block: u64;
let mut success: libc::c_int = 0;
let mut delete_dest_file: libc::c_int = 0;
@@ -757,7 +758,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
let sql = Sql::new();
if sql.open(context, as_path(dest_pathNfilename), 0) {
if sql.open(context, as_path(dest_pathNfilename), 0).is_ok() {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
@@ -768,30 +769,51 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
.is_err()
{
/* error already logged */
ok_to_continue = false;
current_block = 11487273724841241105;
} else {
ok_to_continue = true;
current_block = 14648156034262866959;
}
} else {
ok_to_continue = true;
current_block = 14648156034262866959;
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = std::path::Path::new(as_str(context.get_blobdir()));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
match current_block {
11487273724841241105 => {}
_ => {
let mut total_files_cnt = 0;
let dir = std::path::Path::new(as_str(context.get_blobdir()));
let dir_handle = std::fs::read_dir(dir);
if dir_handle.is_err() {
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
} else {
let dir_handle = dir_handle.unwrap();
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
info!(context, 0, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
if let Ok(dir_handle) = std::fs::read_dir(dir) {
sql.prepare(
info!(context, 0, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
let dir_handle = std::fs::read_dir(dir);
if dir_handle.is_err() {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
} else {
let dir_handle = dir_handle.unwrap();
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
let mut processed_files_cnt = 0;
for entry in dir_handle {
if entry.is_err() {
ok_to_continue = true;
current_block = 2631791190359682872;
break;
}
let entry = entry.unwrap();
@@ -803,7 +825,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
.shall_stop_ongoing
{
delete_dest_file = 1;
ok_to_continue = false;
current_block = 11487273724841241105;
break;
} else {
processed_files_cnt += 1;
@@ -848,7 +870,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
&curr_pathNfilename,
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
current_block = 11487273724841241105;
break;
}
} else {
@@ -860,39 +882,29 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
Ok(())
}
).unwrap();
}
} else {
error!(
context,
0,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
);
info!(context, 0, "Backup: No files to copy.",);
current_block = 2631791190359682872;
}
} else {
info!(context, 0, "Backup: No files to copy.",);
ok_to_continue = true;
}
if ok_to_continue {
if sql
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(
Event::IMEX_FILE_WRITTEN,
dest_pathNfilename as uintptr_t,
0,
);
success = 1;
match current_block {
11487273724841241105 => {}
_ => {
if sql
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(
Event::IMEX_FILE_WRITTEN,
dest_pathNfilename as uintptr_t,
0,
);
success = 1;
}
}
}
}
} else {
error!(
context,
0,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir())
);
};
}
}
}
}
@@ -932,7 +944,16 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
let mut buf2_headerline: *const libc::c_char = ptr::null_mut();
if !dir_name.is_null() {
let dir = std::path::Path::new(as_str(dir_name));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
let dir_handle = std::fs::read_dir(dir);
if dir_handle.is_err() {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
} else {
let dir_handle = dir_handle.unwrap();
for entry in dir_handle {
if entry.is_err() {
break;
@@ -971,9 +992,9 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
if dc_split_armored_data(
buf2,
&mut buf2_headerline,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
) && strcmp(
buf2_headerline,
b"-----BEGIN PGP PUBLIC KEY BLOCK-----\x00" as *const u8 as *const libc::c_char,
@@ -1016,13 +1037,6 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
as_str(dir_name),
);
}
} else {
error!(
context,
0,
"Import: Cannot open directory \"{}\".",
as_str(dir_name),
);
}
}
@@ -1146,7 +1160,7 @@ mod tests {
#[test]
fn test_render_setup_file() {
let t = test_context(Some(logging_cb));
let t = test_context(Some(Box::new(logging_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
@@ -1164,14 +1178,9 @@ mod tests {
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
unsafe extern "C" fn ac_setup_msg_cb(
ctx: &Context,
evt: Event,
d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
fn ac_setup_msg_cb(ctx: &Context, evt: Event, d1: uintptr_t, d2: uintptr_t) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() {
"hello\r\nthere".strdup() as usize
unsafe { "hello\r\nthere".strdup() as usize }
} else {
logging_cb(ctx, evt, d1, d2)
}
@@ -1179,7 +1188,7 @@ mod tests {
#[test]
fn test_render_setup_file_newline_replace() {
let t = test_context(Some(ac_setup_msg_cb));
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);

776
src/dc_location.rs Normal file
View File

@@ -0,0 +1,776 @@
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::job::*;
use crate::message::*;
use crate::param::*;
use crate::sql;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
// location handling
#[derive(Clone, Default)]
#[allow(non_camel_case_types)]
pub struct dc_location {
pub location_id: uint32_t,
pub latitude: libc::c_double,
pub longitude: libc::c_double,
pub accuracy: libc::c_double,
pub timestamp: i64,
pub contact_id: uint32_t,
pub msg_id: uint32_t,
pub chat_id: uint32_t,
pub marker: Option<String>,
pub independent: uint32_t,
}
impl dc_location {
pub fn new() -> Self {
dc_location {
location_id: 0,
latitude: 0.0,
longitude: 0.0,
accuracy: 0.0,
timestamp: 0,
contact_id: 0,
msg_id: 0,
chat_id: 0,
marker: None,
independent: 0,
}
}
}
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct dc_kml_t {
pub addr: *mut libc::c_char,
pub locations: Option<Vec<dc_location>>,
pub tag: libc::c_int,
pub curr: dc_location,
}
impl dc_kml_t {
pub fn new() -> Self {
dc_kml_t {
addr: std::ptr::null_mut(),
locations: None,
tag: 0,
curr: dc_location::new(),
}
}
}
// location streaming
pub unsafe fn dc_send_locations_to_chat(context: &Context, chat_id: uint32_t, seconds: 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 = 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 } 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);
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,
);
}
}
}
}
/*******************************************************************************
* job to send locations out to all chats that want them
******************************************************************************/
#[allow(non_snake_case)]
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
};
}
pub fn dc_is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
context
.sql
.exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
)
.unwrap_or_default()
}
pub fn dc_set_location(
context: &Context,
latitude: libc::c_double,
longitude: libc::c_double,
accuracy: libc::c_double,
) -> libc::c_int {
if latitude == 0.0 && longitude == 0.0 {
return 1;
}
context.sql.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;",
params![time()], |row| row.get::<_, i32>(0),
|chats| {
let mut continue_streaming = false;
for chat in chats {
let chat_id = chat?;
context.sql.execute(
"INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
params![
latitude,
longitude,
accuracy,
time(),
chat_id,
1,
]
)?;
continue_streaming = true;
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
};
unsafe { schedule_MAYBE_SEND_LOCATIONS(context, 0) };
Ok(continue_streaming as libc::c_int)
}
).unwrap_or_default()
}
pub fn dc_get_locations(
context: &Context,
chat_id: uint32_t,
contact_id: uint32_t,
timestamp_from: i64,
mut timestamp_to: i64,
) -> Vec<dc_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 = dc_location {
location_id: row.get(0)?,
latitude: row.get(1)?,
longitude: row.get(2)?,
accuracy: row.get(3)?,
timestamp: row.get(4)?,
independent: row.get(5)?,
msg_id,
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 dc_delete_all_locations(context: &Context) -> bool {
if sql::execute(context, &context.sql, "DELETE FROM locations;", params![]).is_err() {
return false;
}
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
true
}
pub fn dc_get_location_kml(
context: &Context,
chat_id: uint32_t,
last_added_location_id: *mut uint32_t,
) -> *mut libc::c_char {
let mut success: libc::c_int = 0;
let now = time();
let mut location_count: libc::c_int = 0;
let mut ret = String::new();
let self_addr = context
.sql
.get_config(context, "configured_addr")
.unwrap_or_default();
if let Ok((locations_send_begin, locations_send_until, locations_last_sent)) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
params![chat_id as i32], |row| {
let send_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
}
) {
if !(locations_send_begin == 0 || now > locations_send_until) {
ret += &format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
self_addr,
);
context.sql.query_map(
"SELECT id, latitude, longitude, accuracy, timestamp\
FROM locations WHERE from_id=? \
AND timestamp>=? \
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
AND independent=0 \
GROUP BY timestamp \
ORDER BY timestamp;",
params![1, locations_send_begin, locations_last_sent, 1],
|row| {
let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?;
let longitude: f64 = row.get(2)?;
let accuracy: f64 = row.get(3)?;
let timestamp = unsafe { get_kml_timestamp(row.get(4)?) };
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
as_str(timestamp),
accuracy,
longitude,
latitude
);
location_count += 1;
if !last_added_location_id.is_null() {
unsafe { *last_added_location_id = location_id as u32 };
}
unsafe { free(timestamp as *mut libc::c_void) };
}
Ok(())
}
).unwrap(); // TODO: better error handling
}
}
if location_count > 0 {
ret += "</Document>\n</kml>";
success = 1;
}
if 0 != success {
unsafe { ret.strdup() }
} else {
std::ptr::null_mut()
}
}
/*******************************************************************************
* create kml-files
******************************************************************************/
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
.format("%Y-%m-%dT%H:%M:%SZ")
.to_string();
res.strdup()
}
pub unsafe fn dc_get_message_kml(
timestamp: i64,
latitude: libc::c_double,
longitude: libc::c_double,
) -> *mut libc::c_char {
let timestamp_str = get_kml_timestamp(timestamp);
let latitude_str = dc_ftoa(latitude);
let longitude_str = dc_ftoa(longitude);
let ret = dc_mprintf(
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
<Document>\n\
<Placemark>\
<Timestamp><when>%s</when></Timestamp>\
<Point><coordinates>%s,%s</coordinates></Point>\
</Placemark>\n\
</Document>\n\
</kml>\x00" as *const u8 as *const libc::c_char,
timestamp_str,
longitude_str, // reverse order!
latitude_str,
);
free(latitude_str as *mut libc::c_void);
free(longitude_str as *mut libc::c_void);
free(timestamp_str as *mut libc::c_void);
ret
}
pub fn dc_set_kml_sent_timestamp(context: &Context, chat_id: u32, timestamp: i64) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id as i32],
)
.is_ok()
}
pub fn dc_set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> bool {
sql::execute(
context,
&context.sql,
"UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id as i32],
)
.is_ok()
}
pub unsafe fn dc_save_locations(
context: &Context,
chat_id: u32,
contact_id: u32,
locations_opt: &Option<Vec<dc_location>>,
independent: libc::c_int,
) -> u32 {
if chat_id <= 9 || locations_opt.is_none() {
return 0;
}
let locations = locations_opt.as_ref().unwrap();
context
.sql
.prepare2(
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
"INSERT INTO locations\
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
|mut stmt_test, mut stmt_insert, conn| {
let mut newest_timestamp = 0;
let mut newest_location_id = 0;
for location in locations {
let exists =
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if 0 != independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
chat_id as i32,
location.latitude,
location.longitude,
location.accuracy,
independent,
])?;
if location.timestamp > newest_timestamp {
newest_timestamp = location.timestamp;
newest_location_id = sql::get_rowid2_with_conn(
context,
conn,
"locations",
"timestamp",
location.timestamp,
"from_id",
contact_id as i32,
);
}
}
}
Ok(newest_location_id)
},
)
.unwrap_or_default()
}
pub unsafe fn dc_kml_parse(
context: &Context,
content: *const libc::c_char,
content_bytes: size_t,
) -> dc_kml_t {
let mut kml = dc_kml_t::new();
if content_bytes > (1 * 1024 * 1024) {
warn!(
context,
0, "A kml-files with {} bytes is larger than reasonably expected.", content_bytes,
);
return kml;
}
let content_null = dc_null_terminate(content, content_bytes as libc::c_int);
if !content_null.is_null() {
let mut reader = quick_xml::Reader::from_str(as_str(content_null));
reader.trim_text(true);
kml.locations = Some(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, &mut kml, &reader),
Ok(quick_xml::events::Event::End(ref e)) => kml_endtag_cb(e, &mut kml),
Ok(quick_xml::events::Event::Text(ref e)) => kml_text_cb(e, &mut kml, &reader),
Err(e) => {
error!(
context,
0,
"Location parsing: Error at position {}: {:?}",
reader.buffer_position(),
e
);
}
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
buf.clear();
}
}
free(content_null.cast());
kml
}
fn kml_text_cb<B: std::io::BufRead>(
event: &BytesText,
kml: &mut dc_kml_t,
reader: &quick_xml::Reader<B>,
) {
if 0 != kml.tag & (0x4 | 0x10) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let val = val
.replace("\n", "")
.replace("\r", "")
.replace("\t", "")
.replace(" ", "");
if 0 != kml.tag & 0x4 && 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) => {
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 parts = val.splitn(2, ',').collect::<Vec<_>>();
if parts.len() == 2 {
kml.curr.longitude = parts[0].parse().unwrap_or_default();
kml.curr.latitude = parts[1].parse().unwrap_or_default();
}
}
}
}
fn kml_endtag_cb(event: &BytesEnd, kml: &mut dc_kml_t) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
if tag == "placemark" {
if 0 != kml.tag & 0x1
&& 0 != kml.curr.timestamp
&& 0. != kml.curr.latitude
&& 0. != kml.curr.longitude
{
if let Some(ref mut locations) = kml.locations {
locations.push(std::mem::replace(&mut kml.curr, dc_location::new()));
}
}
kml.tag = 0
};
}
fn kml_starttag_cb<B: std::io::BufRead>(
event: &BytesStart,
kml: &mut dc_kml_t,
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()
}) {
kml.addr = unsafe {
addr.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.strdup()
};
}
} else if tag == "placemark" {
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 tag == "timestamp" && 0 != kml.tag & 0x1 {
kml.tag = 0x1 | 0x2
} else if tag == "when" && 0 != kml.tag & 0x2 {
kml.tag = 0x1 | 0x2 | 0x4
} else if tag == "point" && 0 != kml.tag & 0x1 {
kml.tag = 0x1 | 0x8
} else if tag == "coordinates" && 0 != kml.tag & 0x8 {
kml.tag = 0x1 | 0x8 | 0x10;
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();
kml.curr.accuracy = v.trim().parse().unwrap_or_default();
}
}
}
pub unsafe fn dc_kml_unref(kml: &mut dc_kml_t) {
free(kml.addr as *mut libc::c_void);
}
#[allow(non_snake_case)]
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &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
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 unsafe fn dc_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_dc_kml_parse() {
unsafe {
let context = dummy_context();
let xml =
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>\x00"
as *const u8 as *const libc::c_char;
let mut kml = dc_kml_parse(&context.ctx, xml, strlen(xml));
assert!(!kml.addr.is_null());
assert_eq!(as_str(kml.addr as *const libc::c_char), "user@example.org",);
let locations_ref = &kml.locations.as_ref().unwrap();
assert_eq!(locations_ref.len(), 2);
assert!(locations_ref[0].latitude > 53.6f64);
assert!(locations_ref[0].latitude < 53.8f64);
assert!(locations_ref[0].longitude > 9.3f64);
assert!(locations_ref[0].longitude < 9.5f64);
assert!(locations_ref[0].accuracy > 31.9f64);
assert!(locations_ref[0].accuracy < 32.1f64);
assert_eq!(locations_ref[0].timestamp, 1551906597);
assert!(locations_ref[1].latitude > 63.6f64);
assert!(locations_ref[1].latitude < 63.8f64);
assert!(locations_ref[1].longitude > 19.3f64);
assert!(locations_ref[1].longitude < 19.5f64);
assert!(locations_ref[1].accuracy > 2.4f64);
assert!(locations_ref[1].accuracy < 2.6f64);
assert_eq!(locations_ref[1].timestamp, 1544739072);
dc_kml_unref(&mut kml);
}
}
}

View File

@@ -15,11 +15,11 @@ use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{dc_get_version_str, Context};
use crate::dc_e2ee::*;
use crate::dc_location::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::error::Error;
use crate::location;
use crate::message::*;
use crate::param::*;
use crate::stock::StockMessage;
@@ -87,7 +87,7 @@ pub unsafe fn dc_mimefactory_load_msg(
context: &Context,
msg_id: u32,
) -> Result<dc_mimefactory_t, Error> {
ensure!(msg_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
ensure!(msg_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat id");
let msg = dc_msg_load_from_db(context, msg_id)?;
let chat = Chat::load_from_db(context, msg.chat_id)?;
@@ -187,7 +187,7 @@ pub unsafe fn dc_mimefactory_load_msg(
clist_insert_after(
factory.recipients_names,
(*factory.recipients_names).last,
ptr::null_mut(),
0 as *mut libc::c_void,
);
clist_insert_after(
factory.recipients_addr,
@@ -308,7 +308,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
// in dc_markseen_msgs()
ensure!(!contact.is_blocked(), "Contact blocked");
ensure!(
factory.msg.chat_id > DC_CHAT_ID_LAST_SPECIAL,
factory.msg.chat_id > DC_CHAT_ID_LAST_SPECIAL as u32,
"Invalid chat id"
);
@@ -318,7 +318,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
(if !contact.get_authname().is_empty() {
contact.get_authname().strdup()
} else {
ptr::null_mut()
0 as *mut libc::c_char
}) as *mut libc::c_void,
);
clist_insert_after(
@@ -339,10 +339,10 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
let subject: *mut mailimf_subject;
let mut ok_to_continue = true;
let imf_fields: *mut mailimf_fields;
let mut message: *mut mailmime = ptr::null_mut();
let mut message_text: *mut libc::c_char = ptr::null_mut();
let mut message_text2: *mut libc::c_char = ptr::null_mut();
let mut subject_str: *mut libc::c_char = ptr::null_mut();
let mut message: *mut mailmime = 0 as *mut mailmime;
let mut message_text: *mut libc::c_char = 0 as *mut libc::c_char;
let mut message_text2: *mut libc::c_char = 0 as *mut libc::c_char;
let mut subject_str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut afwd_email: libc::c_int = 0;
let mut col: libc::c_int = 0;
let mut success: libc::c_int = 0;
@@ -353,7 +353,13 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
let mut force_plaintext: libc::c_int = 0;
let mut do_gossip: libc::c_int = 0;
let mut grpimage = None;
let mut e2ee_helper = E2eeHelper::default();
let mut e2ee_helper = dc_e2ee_helper_t {
encryption_successfull: 0,
cdata_to_free: 0 as *mut libc::c_void,
encrypted: 0,
signatures: Default::default(),
gossipped_addr: Default::default(),
};
if factory.loaded as libc::c_uint == DC_MF_NOTHING_LOADED as libc::c_int as libc::c_uint
|| !factory.out.is_null()
@@ -371,12 +377,12 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
if !factory.from_displayname.is_null() {
dc_encode_header_words(factory.from_displayname)
} else {
ptr::null_mut()
0 as *mut libc::c_char
},
dc_strdup(factory.from_addr),
),
);
let mut to: *mut mailimf_address_list = ptr::null_mut();
let mut to: *mut mailimf_address_list = 0 as *mut mailimf_address_list;
if !factory.recipients_names.is_null()
&& !factory.recipients_addr.is_null()
&& (*factory.recipients_addr).count > 0
@@ -390,12 +396,12 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
let name: *const libc::c_char = (if !iter1.is_null() {
(*iter1).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *const libc::c_char;
let addr: *const libc::c_char = (if !iter2.is_null() {
(*iter2).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *const libc::c_char;
mailimf_address_list_add(
to,
@@ -405,33 +411,33 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
if !name.is_null() {
dc_encode_header_words(name)
} else {
ptr::null_mut()
0 as *mut libc::c_char
},
dc_strdup(addr),
),
ptr::null_mut(),
0 as *mut mailimf_group,
),
);
iter1 = if !iter1.is_null() {
(*iter1).next
} else {
ptr::null_mut()
0 as *mut clistcell
};
iter2 = if !iter2.is_null() {
(*iter2).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}
let mut references_list: *mut clist = ptr::null_mut();
let mut references_list: *mut clist = 0 as *mut clist;
if !factory.references.is_null() && 0 != *factory.references.offset(0isize) as libc::c_int {
references_list = dc_str_to_clist(
factory.references,
b" \x00" as *const u8 as *const libc::c_char,
)
}
let mut in_reply_to_list: *mut clist = ptr::null_mut();
let mut in_reply_to_list: *mut clist = 0 as *mut clist;
if !factory.in_reply_to.is_null() && 0 != *factory.in_reply_to.offset(0isize) as libc::c_int
{
in_reply_to_list = dc_str_to_clist(
@@ -442,15 +448,15 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
imf_fields = mailimf_fields_new_with_data_all(
mailimf_get_date(factory.timestamp as i64),
from,
ptr::null_mut(),
ptr::null_mut(),
0 as *mut mailimf_mailbox,
0 as *mut mailimf_address_list,
to,
ptr::null_mut(),
ptr::null_mut(),
0 as *mut mailimf_address_list,
0 as *mut mailimf_address_list,
dc_strdup(factory.rfc724_mid),
in_reply_to_list,
references_list,
ptr::null_mut(),
0 as *mut libc::c_char,
);
let os_name = &factory.context.os_name;
@@ -497,8 +503,8 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
/* Render a normal message
*********************************************************************/
let chat = factory.chat.as_ref().unwrap();
let mut meta_part: *mut mailmime = ptr::null_mut();
let mut placeholdertext: *mut libc::c_char = ptr::null_mut();
let mut meta_part: *mut mailmime = 0 as *mut mailmime;
let mut placeholdertext: *mut libc::c_char = 0 as *mut libc::c_char;
if chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -730,7 +736,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
meta.type_0 = Viewtype::Image;
meta.param.set(Param::File, grpimage);
let mut filename_as_sent = ptr::null_mut();
let mut filename_as_sent = 0 as *mut libc::c_char;
meta_part = build_body_file(
&meta,
b"group-image\x00" as *const u8 as *const libc::c_char,
@@ -779,7 +785,7 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
}
afwd_email = factory.msg.param.exists(Param::Forwarded) as libc::c_int;
let mut fwdhint = ptr::null_mut();
let mut fwdhint = 0 as *mut libc::c_char;
if 0 != afwd_email {
fwdhint = dc_strdup(
b"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n\x00"
@@ -844,8 +850,11 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
free(error as *mut libc::c_void);
ok_to_continue = false;
} else {
let file_part: *mut mailmime =
build_body_file(&factory.msg, ptr::null(), ptr::null_mut());
let file_part: *mut mailmime = build_body_file(
&factory.msg,
0 as *const libc::c_char,
0 as *mut *mut libc::c_char,
);
if !file_part.is_null() {
mailmime_smart_add_part(message, file_part);
parts += 1
@@ -874,47 +883,45 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
.param
.get_float(Param::SetLongitude)
.unwrap_or_default();
let kml_file = location::get_message_kml(
factory.msg.timestamp_sort,
latitude,
longitude,
);
let content_type = mailmime_content_new_with_str(
b"application/vnd.google-earth.kml+xml\x00" as *const u8
as *const libc::c_char,
);
let mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
dc_strdup(b"message.kml\x00" as *const u8 as *const libc::c_char),
MAILMIME_MECHANISM_8BIT as libc::c_int,
);
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
mailmime_set_body_text(kml_mime_part, kml_file.strdup(), kml_file.len());
mailmime_smart_add_part(message, kml_mime_part);
}
if location::is_sending_locations_to_chat(
factory.msg.context,
factory.msg.chat_id,
) {
if let Ok((kml_file, last_added_location_id)) =
location::get_kml(factory.msg.context, factory.msg.chat_id)
{
let kml_file =
dc_get_message_kml(factory.msg.timestamp_sort, latitude, longitude);
if !kml_file.is_null() {
let content_type = mailmime_content_new_with_str(
b"application/vnd.google-earth.kml+xml\x00" as *const u8
as *const libc::c_char,
);
let mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
dc_strdup(b"location.kml\x00" as *const u8 as *const libc::c_char),
dc_strdup(b"message.kml\x00" as *const u8 as *const libc::c_char),
MAILMIME_MECHANISM_8BIT as libc::c_int,
);
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
mailmime_set_body_text(
kml_mime_part,
kml_file.strdup(),
kml_file.len(),
mailmime_set_body_text(kml_mime_part, kml_file, strlen(kml_file));
mailmime_smart_add_part(message, kml_mime_part);
}
}
if dc_is_sending_locations_to_chat(factory.msg.context, factory.msg.chat_id) {
let mut last_added_location_id: uint32_t = 0 as uint32_t;
let kml_file: *mut libc::c_char = dc_get_location_kml(
factory.msg.context,
factory.msg.chat_id,
&mut last_added_location_id,
);
if !kml_file.is_null() {
let content_type: *mut mailmime_content = mailmime_content_new_with_str(
b"application/vnd.google-earth.kml+xml\x00" as *const u8
as *const libc::c_char,
);
let mime_fields: *mut mailmime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
dc_strdup(b"location.kml\x00" as *const u8 as *const libc::c_char),
MAILMIME_MECHANISM_8BIT as libc::c_int,
);
let kml_mime_part: *mut mailmime =
mailmime_new_empty(content_type, mime_fields);
mailmime_set_body_text(kml_mime_part, kml_file, strlen(kml_file));
mailmime_smart_add_part(message, kml_mime_part);
if !factory.msg.param.exists(Param::SetLatitude) {
// otherwise, the independent location is already filed
@@ -1002,32 +1009,32 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
imf_fields,
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,
),
);
if force_plaintext != 2 {
e2ee_helper.encrypt(
dc_e2ee_encrypt(
factory.context,
factory.recipients_addr,
force_plaintext,
@@ -1035,9 +1042,10 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
min_verified,
do_gossip,
message,
&mut e2ee_helper,
);
}
if e2ee_helper.encryption_successfull {
if 0 != e2ee_helper.encryption_successfull {
factory.out_encrypted = 1;
if 0 != do_gossip {
factory.out_gossiped = 1
@@ -1045,14 +1053,14 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
}
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
mailmime_write_mem(factory.out, &mut col, message);
success = 1;
success = 1
}
}
if !message.is_null() {
mailmime_free(message);
}
e2ee_helper.thanks();
dc_e2ee_thanks(&mut e2ee_helper);
free(message_text as *mut libc::c_void);
free(message_text2 as *mut libc::c_void);
free(subject_str as *mut libc::c_void);
@@ -1141,7 +1149,7 @@ unsafe fn build_body_file(
) -> *mut mailmime {
let needs_ext: bool;
let mime_fields: *mut mailmime_fields;
let mut mime_sub: *mut mailmime = ptr::null_mut();
let mut mime_sub: *mut mailmime = 0 as *mut mailmime;
let content: *mut mailmime_content;
let path_filename = msg.param.get(Param::File);
@@ -1151,8 +1159,8 @@ unsafe fn build_body_file(
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
let mut filename_to_send = ptr::null_mut();
let mut filename_encoded = ptr::null_mut();
let mut filename_to_send = 0 as *mut libc::c_char;
let mut filename_encoded = 0 as *mut libc::c_char;
if let Some(ref path_filename) = path_filename {
let suffix = dc_get_filesuffix_lc(path_filename);
@@ -1222,7 +1230,7 @@ unsafe fn build_body_file(
mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
if needs_ext {
ptr::null_mut()
0 as *mut libc::c_char
} else {
dc_strdup(filename_to_send)
},
@@ -1234,7 +1242,7 @@ unsafe fn build_body_file(
let field: *mut mailmime_field = (if !cur1.is_null() {
(*cur1).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailmime_field;
if !field.is_null()
&& (*field).fld_type == MAILMIME_FIELD_DISPOSITION as libc::c_int
@@ -1246,10 +1254,10 @@ unsafe fn build_body_file(
let parm: *mut mailmime_disposition_parm =
mailmime_disposition_parm_new(
MAILMIME_DISPOSITION_PARM_PARAMETER as libc::c_int,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as *mut libc::c_char,
0 as *mut libc::c_char,
0 as *mut libc::c_char,
0 as *mut libc::c_char,
0 as size_t,
mailmime_parameter_new(
strdup(
@@ -1271,7 +1279,7 @@ unsafe fn build_body_file(
cur1 = if !cur1.is_null() {
(*cur1).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}

File diff suppressed because it is too large Load Diff

44
src/dc_move.rs Normal file
View File

@@ -0,0 +1,44 @@
use crate::constants::*;
use crate::context::*;
use crate::job::*;
use crate::message::*;
use crate::param::Params;
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
if context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
{
return;
}
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
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);
}
}
}

View File

@@ -13,14 +13,15 @@ use sha2::{Digest, Sha256};
use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{do_heuristics_moves, Context};
use crate::context::Context;
use crate::dc_location::*;
use crate::dc_mimeparser::*;
use crate::dc_move::*;
use crate::dc_securejoin::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::error::Result;
use crate::job::*;
use crate::location;
use crate::message::*;
use crate::param::*;
use crate::peerstate::*;
@@ -55,8 +56,8 @@ pub unsafe fn dc_receive_imf(
// we use mailmime_parse() through dc_mimeparser (both call mailimf_struct_multiple_parse()
// somewhen, I did not found out anything that speaks against this approach yet)
let body = std::slice::from_raw_parts(imf_raw_not_terminated as *const u8, imf_raw_bytes);
let mut mime_parser = dc_mimeparser_parse(context, body);
let mut mime_parser = dc_mimeparser_new(context);
dc_mimeparser_parse(&mut mime_parser, imf_raw_not_terminated, imf_raw_bytes);
if mime_parser.header.is_empty() {
// Error - even adding an empty record won't help as we do not know the message ID
@@ -130,7 +131,7 @@ pub unsafe fn dc_receive_imf(
if 0 != check_self {
incoming = 0;
if 0 != dc_mimeparser_sender_equals_recipient(&mime_parser) {
from_id = DC_CONTACT_ID_SELF;
from_id = DC_CONTACT_ID_SELF as u32;
}
} else if from_list.len() >= 1 {
// if there is no from given, from_id stays 0 which is just fine. These messages
@@ -219,7 +220,7 @@ pub unsafe fn dc_receive_imf(
);
}
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL {
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as u32 {
save_locations(
context,
&mime_parser,
@@ -361,7 +362,7 @@ unsafe fn add_parts(
}
// 1 or 0 for yes/no
msgrmsg = mime_parser.is_send_by_messenger as _;
msgrmsg = mime_parser.is_send_by_messenger;
if msgrmsg == 0 && 0 != dc_is_reply_to_messenger_message(context, mime_parser) {
// 2=no, but is reply to messenger message
msgrmsg = 2;
@@ -444,7 +445,7 @@ unsafe fn add_parts(
if *chat_id == 0 {
// check if the message belongs to a mailing list
if dc_mimeparser_is_mailinglist_message(mime_parser) {
if 0 != dc_mimeparser_is_mailinglist_message(mime_parser) {
*chat_id = 3;
info!(
context,
@@ -491,7 +492,7 @@ unsafe fn add_parts(
}
if *chat_id == 0 {
// maybe from_id is null or sth. else is suspicious, move message to trash
*chat_id = DC_CHAT_ID_TRASH;
*chat_id = DC_CHAT_ID_TRASH as u32;
}
// if the chat_id is blocked,
@@ -508,7 +509,7 @@ unsafe fn add_parts(
// the mail is on the IMAP server, probably it is also delivered.
// We cannot recreate other states (read, error).
state = MessageState::OutDelivered;
*from_id = DC_CONTACT_ID_SELF;
*from_id = DC_CONTACT_ID_SELF as u32;
if !to_ids.is_empty() {
*to_id = to_ids[0];
if *chat_id == 0 {
@@ -564,7 +565,7 @@ unsafe fn add_parts(
}
}
if *chat_id == 0 {
*chat_id = DC_CHAT_ID_TRASH;
*chat_id = DC_CHAT_ID_TRASH as u32;
}
}
// correct message_timestamp, it should not be used before,
@@ -629,18 +630,21 @@ unsafe fn add_parts(
continue;
}
if let Some(ref msg) = part.msg {
if !mime_parser.location_kml.is_none()
&& icnt == 1
&& (msg == "-location-" || msg.is_empty())
{
*hidden = 1;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
if !mime_parser.location_kml.is_none()
&& icnt == 1
&& !part.msg.is_null()
&& (strcmp(
part.msg,
b"-location-\x00" as *const u8 as *const libc::c_char,
) == 0
|| *part.msg.offset(0) as libc::c_int == 0)
{
*hidden = 1;
if state == MessageState::InFresh {
state = MessageState::InNoticed;
}
}
if part.type_0 == Viewtype::Text {
if part.type_0 == Viewtype::Text as i32 {
txt_raw = dc_mprintf(
b"%s\n\n%s\x00" as *const u8 as *const libc::c_char,
if !mime_parser.subject.is_null() {
@@ -669,7 +673,11 @@ unsafe fn add_parts(
part.type_0,
state,
msgrmsg,
part.msg.as_ref().map_or("", String::as_str),
if !part.msg.is_null() {
as_str(part.msg)
} else {
""
},
// txt_raw might contain invalid utf8
if !txt_raw.is_null() {
to_string_lossy(txt_raw)
@@ -695,7 +703,7 @@ unsafe fn add_parts(
])?;
free(txt_raw as *mut libc::c_void);
txt_raw = ptr::null_mut();
txt_raw = 0 as *mut libc::c_char;
*insert_msg_id = sql::get_rowid_with_conn(
context,
conn,
@@ -722,7 +730,7 @@ unsafe fn add_parts(
);
// check event to send
if *chat_id == DC_CHAT_ID_TRASH {
if *chat_id == DC_CHAT_ID_TRASH as u32 {
*create_event_to_send = None;
} else if 0 != incoming && state == MessageState::InFresh {
if 0 != from_id_blocked {
@@ -734,7 +742,7 @@ unsafe fn add_parts(
}
}
do_heuristics_moves(context, server_folder.as_ref(), *insert_msg_id);
dc_do_heuristics_moves(context, server_folder.as_ref(), *insert_msg_id);
cleanup(mime_in_reply_to, mime_references, txt_raw);
Ok(())
@@ -796,7 +804,7 @@ unsafe fn handle_reports(
{
(*(*(*report_root).mm_data.mm_multipart.mm_mp_list).first).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
.is_null()
{
@@ -806,11 +814,11 @@ unsafe fn handle_reports(
{
(*(*(*report_root).mm_data.mm_multipart.mm_mp_list).first).next
} else {
ptr::null_mut()
0 as *mut clistcell
})
.data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailmime;
if !report_data.is_null()
@@ -900,11 +908,11 @@ unsafe fn handle_reports(
}
}
if mime_parser.is_send_by_messenger || 0 != mdn_consumed {
if 0 != mime_parser.is_send_by_messenger || 0 != mdn_consumed {
let mut param = Params::new();
param.set(Param::ServerFolder, server_folder.as_ref());
param.set_int(Param::ServerUid, server_uid as i32);
if mime_parser.is_send_by_messenger
if 0 != mime_parser.is_send_by_messenger
&& 0 != context
.sql
.get_config_int(context, "mvbox_move")
@@ -930,34 +938,36 @@ unsafe fn save_locations(
let mut send_event = false;
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
let newest_location_id: uint32_t = dc_save_locations(
context,
chat_id,
from_id,
&mime_parser.message_kml.as_ref().unwrap().locations,
1,
);
if 0 != newest_location_id && 0 == hidden {
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
location_id_written = true;
send_event = true;
}
dc_set_msg_location_id(context, insert_msg_id, newest_location_id);
location_id_written = true;
send_event = true;
}
}
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
if !mime_parser.location_kml.as_ref().unwrap().addr.is_null() {
if let Ok(contact) = Contact::get_by_id(context, from_id) {
if !contact.get_addr().is_empty()
&& contact.get_addr().to_lowercase() == addr.to_lowercase()
&& contact.get_addr().to_lowercase()
== as_str(mime_parser.location_kml.as_ref().unwrap().addr).to_lowercase()
{
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
let newest_location_id = dc_save_locations(
context,
chat_id,
from_id,
&mime_parser.location_kml.as_ref().unwrap().locations,
0,
);
if newest_location_id != 0 && hidden == 0 && !location_id_written {
if let Err(err) = location::set_msg_location_id(
context,
insert_msg_id,
newest_location_id,
) {
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
}
dc_set_msg_location_id(context, insert_msg_id, newest_location_id);
}
send_event = true;
}
@@ -1250,7 +1260,7 @@ unsafe fn create_or_lookup_group(
.get_config(context, "configured_addr")
.unwrap_or_default();
if chat_id == 0
&& !dc_mimeparser_is_mailinglist_message(mime_parser)
&& 0 == dc_mimeparser_is_mailinglist_message(mime_parser)
&& !grpid.is_empty()
&& !grpname.is_null()
// otherwise, a pending "quit" message may pop up
@@ -1289,10 +1299,10 @@ unsafe fn create_or_lookup_group(
}
// again, check chat_id
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 {
chat_id = 0;
if group_explicitly_left {
chat_id = DC_CHAT_ID_TRASH;
chat_id = DC_CHAT_ID_TRASH as u32;
} else {
create_or_lookup_adhoc_group(
context,
@@ -1333,7 +1343,7 @@ unsafe fn create_or_lookup_group(
}
if !X_MrGrpImageChanged.is_null() {
let mut ok = 0;
let mut grpimage = ptr::null_mut();
let mut grpimage = 0 as *mut libc::c_char;
if strcmp(
X_MrGrpImageChanged,
b"0\x00" as *const u8 as *const libc::c_char,
@@ -1342,7 +1352,7 @@ unsafe fn create_or_lookup_group(
ok = 1
} else {
for part in &mut mime_parser.parts {
if part.type_0 == Viewtype::Image {
if part.type_0 == 20 {
grpimage = part
.param
.get(Param::File)
@@ -1387,7 +1397,7 @@ unsafe fn create_or_lookup_group(
let skip = if !X_MrRemoveFromGrp.is_null() {
X_MrRemoveFromGrp
} else {
ptr::null_mut()
0 as *mut libc::c_char
};
sql::execute(
context,
@@ -1397,9 +1407,9 @@ unsafe fn create_or_lookup_group(
)
.ok();
if skip.is_null() || !addr_cmp(&self_addr, as_str(skip)) {
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF);
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF as u32);
}
if from_id > DC_CHAT_ID_LAST_SPECIAL {
if from_id > DC_CHAT_ID_LAST_SPECIAL as u32 {
if !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
&& (skip.is_null()
|| !Contact::addr_equals_contact(context, to_string(skip), from_id as u32))
@@ -1425,7 +1435,7 @@ unsafe fn create_or_lookup_group(
// check the number of receivers -
// the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */
if to_ids_cnt == 1 && !mime_parser.is_send_by_messenger {
if to_ids_cnt == 1 && mime_parser.is_send_by_messenger == 0 {
let is_contact_cnt = chat::get_chat_contact_cnt(context, chat_id);
if is_contact_cnt > 3 {
// to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt.
@@ -1469,7 +1479,7 @@ unsafe fn create_or_lookup_adhoc_group(
// group matching the to-list or if we can create one
let mut chat_id = 0;
let mut chat_id_blocked = Blocked::Not;
let mut grpname = ptr::null_mut();
let mut grpname = 0 as *mut libc::c_char;
let cleanup = |grpname: *mut libc::c_char,
ret_chat_id: *mut uint32_t,
@@ -1485,7 +1495,7 @@ unsafe fn create_or_lookup_adhoc_group(
};
// build member list from the given ids
if to_ids.is_empty() || dc_mimeparser_is_mailinglist_message(mime_parser) {
if to_ids.is_empty() || 0 != dc_mimeparser_is_mailinglist_message(mime_parser) {
// too few contacts or a mailinglist
cleanup(
grpname,
@@ -1761,7 +1771,7 @@ unsafe fn check_verified_properties(
}
};
if mimeparser.e2ee_helper.encrypted {
if 0 == mimeparser.e2ee_helper.encrypted {
verify_fail("This message is not encrypted".into());
return 0;
}
@@ -1852,8 +1862,9 @@ unsafe fn set_better_msg<T: AsRef<str>>(mime_parser: &mut dc_mimeparser_t, bette
let msg = better_msg.as_ref();
if msg.len() > 0 && !mime_parser.parts.is_empty() {
let part = &mut mime_parser.parts[0];
if (*part).type_0 == Viewtype::Text {
part.msg = Some(msg.to_string());
if (*part).type_0 == 10 {
free(part.msg as *mut libc::c_void);
part.msg = msg.strdup();
}
};
}
@@ -1874,7 +1885,10 @@ unsafe fn dc_is_reply_to_known_message(
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_IN_REPLY_TO as libc::c_int {
let fld_in_reply_to: *mut mailimf_in_reply_to = (*field).fld_data.fld_in_reply_to;
if !fld_in_reply_to.is_null() {
if is_known_rfc724_mid_in_list(context, (*(*field).fld_data.fld_in_reply_to).mid_list) {
if 0 != is_known_rfc724_mid_in_list(
context,
(*(*field).fld_data.fld_in_reply_to).mid_list,
) {
return 1;
}
}
@@ -1883,7 +1897,10 @@ unsafe fn dc_is_reply_to_known_message(
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
let fld_references: *mut mailimf_references = (*field).fld_data.fld_references;
if !fld_references.is_null() {
if is_known_rfc724_mid_in_list(context, (*(*field).fld_data.fld_references).mid_list) {
if 0 != is_known_rfc724_mid_in_list(
context,
(*(*field).fld_data.fld_references).mid_list,
) {
return 1;
}
}
@@ -1891,18 +1908,29 @@ unsafe fn dc_is_reply_to_known_message(
0
}
unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist) -> bool {
if mid_list.is_null() {
return false;
}
for data in &*mid_list {
if 0 != is_known_rfc724_mid(context, data.cast()) {
return true;
unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist) -> libc::c_int {
if !mid_list.is_null() {
let mut cur: *mut clistiter;
cur = (*mid_list).first;
while !cur.is_null() {
if 0 != is_known_rfc724_mid(
context,
(if !cur.is_null() {
(*cur).data
} else {
0 as *mut libc::c_void
}) as *const libc::c_char,
) {
return 1;
}
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
}
}
}
return false;
0
}
/// Check if a message is a reply to a known message (messenger or non-messenger).
@@ -1967,7 +1995,7 @@ unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clis
(if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *const libc::c_char,
) {
return 1;
@@ -2011,7 +2039,7 @@ unsafe fn dc_add_or_lookup_contacts_by_address_list(
let adr: *mut mailimf_address = (if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailimf_address;
if !adr.is_null() {
if (*adr).ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
@@ -2042,7 +2070,7 @@ unsafe fn dc_add_or_lookup_contacts_by_address_list(
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}
@@ -2062,7 +2090,7 @@ unsafe fn dc_add_or_lookup_contacts_by_mailbox_list(
let mb: *mut mailimf_mailbox = (if !cur.is_null() {
(*cur).data
} else {
ptr::null_mut()
0 as *mut libc::c_void
}) as *mut mailimf_mailbox;
if !mb.is_null() {
add_or_lookup_contact_by_addr(
@@ -2077,7 +2105,7 @@ unsafe fn dc_add_or_lookup_contacts_by_mailbox_list(
cur = if !cur.is_null() {
(*cur).next
} else {
ptr::null_mut()
0 as *mut clistcell
}
}
}

View File

@@ -1,6 +1,5 @@
use mmime::mailimf_types::*;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use std::ptr;
use crate::aheader::EncryptPreference;
use crate::chat::{self, Chat};
@@ -8,10 +7,10 @@ use crate::configure::*;
use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_e2ee::*;
use crate::dc_mimeparser::*;
use crate::dc_token::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::key::*;
use crate::lot::LotState;
use crate::message::*;
@@ -30,41 +29,40 @@ pub unsafe fn dc_get_securejoin_qr(
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
========================================================= */
let cleanup = |fingerprint, qr: Option<String>| {
let mut fingerprint = 0 as *mut libc::c_char;
let mut invitenumber: *mut libc::c_char;
let mut auth: *mut libc::c_char;
let mut qr: Option<String> = None;
dc_ensure_secret_key_exists(context).ok();
invitenumber = dc_token_lookup(context, DC_TOKEN_INVITENUMBER, group_chat_id);
if invitenumber.is_null() {
invitenumber = dc_create_id().strdup();
dc_token_save(context, DC_TOKEN_INVITENUMBER, group_chat_id, invitenumber);
}
auth = dc_token_lookup(context, DC_TOKEN_AUTH, group_chat_id);
if auth.is_null() {
auth = dc_create_id().strdup();
dc_token_save(context, DC_TOKEN_AUTH, group_chat_id, auth);
}
let self_addr = context.sql.get_config(context, "configured_addr");
let cleanup = |fingerprint| {
free(fingerprint as *mut libc::c_void);
free(invitenumber as *mut libc::c_void);
free(auth as *mut libc::c_void);
if let Some(qr) = qr {
qr.strdup()
} else {
"".strdup()
std::ptr::null_mut()
}
};
let mut fingerprint = ptr::null_mut();
let mut qr: Option<String> = None;
ensure_secret_key_exists(context).ok();
let invitenumber = dc_token_lookup(context, DC_TOKEN_INVITENUMBER, group_chat_id)
.unwrap_or_else(|| {
let invitenumber_s = dc_create_id();
dc_token_save(
context,
DC_TOKEN_INVITENUMBER,
group_chat_id,
&invitenumber_s,
);
invitenumber_s
});
let auth = dc_token_lookup(context, DC_TOKEN_AUTH, group_chat_id).unwrap_or_else(|| {
let auth_s = dc_create_id();
dc_token_save(context, DC_TOKEN_AUTH, group_chat_id, &auth_s);
auth_s
});
let self_addr = context.sql.get_config(context, "configured_addr");
if self_addr.is_none() {
error!(context, 0, "Not configured, cannot generate QR code.",);
return cleanup(fingerprint, qr);
return cleanup(fingerprint);
}
let self_addr = self_addr.unwrap();
@@ -76,7 +74,7 @@ pub unsafe fn dc_get_securejoin_qr(
fingerprint = get_self_fingerprint(context);
if fingerprint.is_null() {
return cleanup(fingerprint, qr);
return cleanup(fingerprint);
}
let self_addr_urlencoded = utf8_percent_encode(&self_addr, NON_ALPHANUMERIC).to_string();
@@ -94,15 +92,15 @@ pub unsafe fn dc_get_securejoin_qr(
self_addr_urlencoded,
&group_name_urlencoded,
&chat.grpid,
&invitenumber,
&auth,
as_str(invitenumber),
as_str(auth),
))
} else {
error!(
context,
0, "Cannot get QR-code for chat-id {}", group_chat_id,
);
return cleanup(fingerprint, qr);
return cleanup(fingerprint);
}
} else {
Some(format!(
@@ -110,14 +108,14 @@ pub unsafe fn dc_get_securejoin_qr(
as_str(fingerprint),
self_addr_urlencoded,
self_name_urlencoded,
&invitenumber,
&auth,
as_str(invitenumber),
as_str(auth),
))
};
info!(context, 0, "Generated QR code: {}", qr.as_ref().unwrap());
return cleanup(fingerprint, qr);
cleanup(fingerprint)
}
fn get_self_fingerprint(context: &Context) -> *mut libc::c_char {
@@ -141,7 +139,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) ->
let mut join_vg: libc::c_int = 0i32;
info!(context, 0, "Requesting secure-join ...",);
ensure_secret_key_exists(context).ok();
dc_ensure_secret_key_exists(context).ok();
ongoing_allocated = dc_alloc_ongoing(context);
if !(ongoing_allocated == 0i32) {
@@ -246,7 +244,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) ->
.invitenumber
.as_ref()
.unwrap(),
ptr::null(),
0 as *const libc::c_char,
"",
);
}
@@ -272,7 +270,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) ->
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
None,
ptr::null_mut(),
0 as *mut libc::c_int,
) as libc::c_int
} else {
ret_chat_id = contact_chat_id as libc::c_int
@@ -370,10 +368,10 @@ pub unsafe fn dc_handle_securejoin_handshake(
mimeparser: &dc_mimeparser_t,
contact_id: uint32_t,
) -> libc::c_int {
let mut ok_to_continue: bool;
let mut current_block: u64;
let step: *const libc::c_char;
let join_vg: libc::c_int;
let mut own_fingerprint: *mut libc::c_char = ptr::null_mut();
let mut own_fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
let contact_chat_id: u32;
let contact_chat_id_blocked: Blocked;
let mut grpid = "".to_string();
@@ -414,10 +412,10 @@ pub unsafe fn dc_handle_securejoin_handshake(
invitenumber = lookup_field(mimeparser, "Secure-Join-Invitenumber");
if invitenumber.is_null() {
warn!(context, 0, "Secure-join denied (invitenumber missing).",);
ok_to_continue = false;
} else if !dc_token_exists(context, DC_TOKEN_INVITENUMBER, as_str(invitenumber)) {
current_block = 4378276786830486580;
} else if !dc_token_exists(context, DC_TOKEN_INVITENUMBER, invitenumber) {
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
info!(context, 0, "Secure-join requested.",);
@@ -435,10 +433,10 @@ pub unsafe fn dc_handle_securejoin_handshake(
b"vc-auth-required\x00" as *const u8 as *const libc::c_char
},
"",
ptr::null(),
0 as *const libc::c_char,
"",
);
ok_to_continue = true;
current_block = 10256747982273457880;
}
} else if strcmp(
step,
@@ -460,7 +458,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
if cond {
warn!(context, 0, "auth-required message out of sync.",);
// no error, just aborted somehow or a mail from another handshake
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
let scanned_fingerprint_of_alice = context
.bob
@@ -503,14 +501,14 @@ pub unsafe fn dc_handle_securejoin_handshake(
could_not_establish_secure_connection(
context,
contact_chat_id,
if mimeparser.e2ee_helper.encrypted {
if 0 != mimeparser.e2ee_helper.encrypted {
b"No valid signature.\x00" as *const u8 as *const libc::c_char
} else {
b"Not encrypted.\x00" as *const u8 as *const libc::c_char
},
);
end_bobs_joining(context, 0i32);
ok_to_continue = false;
current_block = 4378276786830486580;
} else if 0
== fingerprint_equals_sender(
context,
@@ -525,7 +523,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
as *const libc::c_char,
);
end_bobs_joining(context, 0i32);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
info!(context, 0, "Fingerprint verified.",);
own_fingerprint = get_self_fingerprint(context);
@@ -548,7 +546,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
own_fingerprint,
grpid,
);
ok_to_continue = true;
current_block = 10256747982273457880;
}
}
} else if strcmp(
@@ -573,14 +571,14 @@ pub unsafe fn dc_handle_securejoin_handshake(
contact_chat_id,
b"Fingerprint not provided.\x00" as *const u8 as *const libc::c_char,
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else if !encrypted_and_signed(mimeparser, as_str(fingerprint)) {
could_not_establish_secure_connection(
context,
contact_chat_id,
b"Auth not encrypted.\x00" as *const u8 as *const libc::c_char,
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else if 0
== fingerprint_equals_sender(context, as_str(fingerprint), contact_chat_id)
{
@@ -590,7 +588,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
b"Fingerprint mismatch on inviter-side.\x00" as *const u8
as *const libc::c_char,
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
info!(context, 0, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
@@ -602,14 +600,14 @@ pub unsafe fn dc_handle_securejoin_handshake(
contact_chat_id,
b"Auth not provided.\x00" as *const u8 as *const libc::c_char,
);
ok_to_continue = false;
} else if !dc_token_exists(context, DC_TOKEN_AUTH, as_str(auth_0)) {
current_block = 4378276786830486580;
} else if !dc_token_exists(context, DC_TOKEN_AUTH, auth_0) {
could_not_establish_secure_connection(
context,
contact_chat_id,
b"Auth invalid.\x00" as *const u8 as *const libc::c_char,
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else if 0 == mark_peer_as_verified(context, as_str(fingerprint)) {
could_not_establish_secure_connection(
context,
@@ -617,7 +615,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
b"Fingerprint mismatch on inviter-side.\x00" as *const u8
as *const libc::c_char,
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
Contact::scaleup_origin_by_id(
context,
@@ -638,11 +636,15 @@ pub unsafe fn dc_handle_securejoin_handshake(
);
if 0 != join_vg {
grpid = to_string(lookup_field(mimeparser, "Secure-Join-Group"));
let group_chat_id: uint32_t =
chat::get_chat_id_by_grpid(context, &grpid, None, ptr::null_mut());
let group_chat_id: uint32_t = chat::get_chat_id_by_grpid(
context,
&grpid,
None,
0 as *mut libc::c_int,
);
if group_chat_id == 0i32 as libc::c_uint {
error!(context, 0, "Chat {} not found.", &grpid);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
chat::add_contact_to_chat_ex(
context,
@@ -650,7 +652,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
contact_id,
0x1i32,
);
ok_to_continue = true;
current_block = 10256747982273457880;
}
} else {
send_handshake_msg(
@@ -658,7 +660,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
contact_chat_id,
b"vc-contact-confirm\x00" as *const u8 as *const libc::c_char,
"",
ptr::null(),
0 as *const libc::c_char,
"",
);
context.call_cb(
@@ -666,7 +668,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
contact_id as uintptr_t,
1000i32 as uintptr_t,
);
ok_to_continue = true;
current_block = 10256747982273457880;
}
}
}
@@ -684,7 +686,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
}
if context.bob.read().unwrap().expects != 6 {
info!(context, 0, "Message belongs to a different handshake.",);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
let cond = {
let bob = context.bob.read().unwrap();
@@ -697,7 +699,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
context,
0, "Message out of sync or belongs to a different handshake.",
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
let scanned_fingerprint_of_alice = context
.bob
@@ -747,66 +749,78 @@ pub unsafe fn dc_handle_securejoin_handshake(
as *const libc::c_char,
);
end_bobs_joining(context, 0i32);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
ok_to_continue = true;
current_block = 5195798230510548452;
}
} else {
ok_to_continue = true;
current_block = 5195798230510548452;
}
if ok_to_continue {
if 0 == mark_peer_as_verified(context, &scanned_fingerprint_of_alice) {
could_not_establish_secure_connection(
match current_block {
4378276786830486580 => {}
_ => {
if 0 == mark_peer_as_verified(
context,
contact_chat_id,
b"Fingerprint mismatch on joiner-side.\x00" as *const u8
as *const libc::c_char,
);
ok_to_continue = false;
} else {
Contact::scaleup_origin_by_id(
context,
contact_id,
Origin::SecurejoinJoined,
);
context.call_cb(
Event::CONTACTS_CHANGED,
0i32 as uintptr_t,
0i32 as uintptr_t,
);
if 0 != join_vg {
if !addr_equals_self(
&scanned_fingerprint_of_alice,
) {
could_not_establish_secure_connection(
context,
as_str(lookup_field(mimeparser, "Chat-Group-Member-Added")),
) {
info!(
contact_chat_id,
b"Fingerprint mismatch on joiner-side.\x00" as *const u8
as *const libc::c_char,
);
current_block = 4378276786830486580;
} else {
Contact::scaleup_origin_by_id(
context,
contact_id,
Origin::SecurejoinJoined,
);
context.call_cb(
Event::CONTACTS_CHANGED,
0i32 as uintptr_t,
0i32 as uintptr_t,
);
if 0 != join_vg {
if !addr_equals_self(
context,
as_str(lookup_field(
mimeparser,
"Chat-Group-Member-Added",
)),
) {
info!(
context,
0,
"Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."
);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
current_block = 9180031981464905198;
}
} else {
ok_to_continue = true;
current_block = 9180031981464905198;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
secure_connection_established(context, contact_chat_id);
context.bob.write().unwrap().expects = 0;
if 0 != join_vg {
send_handshake_msg(
context,
contact_chat_id,
b"vg-member-added-received\x00" as *const u8
as *const libc::c_char,
"",
ptr::null(),
"",
);
match current_block {
4378276786830486580 => {}
_ => {
secure_connection_established(context, contact_chat_id);
context.bob.write().unwrap().expects = 0;
if 0 != join_vg {
send_handshake_msg(
context,
contact_chat_id,
b"vg-member-added-received\x00" as *const u8
as *const libc::c_char,
"",
0 as *const libc::c_char,
"",
);
}
end_bobs_joining(context, 1i32);
current_block = 10256747982273457880;
}
}
end_bobs_joining(context, 1i32);
ok_to_continue = true;
}
}
}
@@ -824,7 +838,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified() == VerifiedStatus::Unverified {
warn!(context, 0, "vg-member-added-received invalid.",);
ok_to_continue = false;
current_block = 4378276786830486580;
} else {
context.call_cb(
Event::SECUREJOIN_INVITER_PROGRESS,
@@ -836,18 +850,21 @@ pub unsafe fn dc_handle_securejoin_handshake(
contact_id as uintptr_t,
1000i32 as uintptr_t,
);
ok_to_continue = true;
current_block = 10256747982273457880;
}
} else {
warn!(context, 0, "vg-member-added-received invalid.",);
ok_to_continue = false;
current_block = 4378276786830486580;
}
} else {
ok_to_continue = true;
current_block = 10256747982273457880;
}
if ok_to_continue {
if 0 != ret & 0x2i32 {
ret |= 0x4i32
match current_block {
4378276786830486580 => {}
_ => {
if 0 != ret & 0x2i32 {
ret |= 0x4i32
}
}
}
}
@@ -881,7 +898,7 @@ unsafe fn secure_connection_established(context: &Context, contact_chat_id: uint
}
unsafe fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> *const libc::c_char {
let mut value: *const libc::c_char = ptr::null();
let mut value: *const libc::c_char = 0 as *const libc::c_char;
let field: *mut mailimf_field = dc_mimeparser_lookup_field(mimeparser, key);
if field.is_null()
|| (*field).fld_type != MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
@@ -891,7 +908,7 @@ unsafe fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> *const libc::
value.is_null()
}
{
return ptr::null();
return 0 as *const libc::c_char;
}
value
@@ -942,7 +959,7 @@ unsafe fn encrypted_and_signed(
mimeparser: &dc_mimeparser_t,
expected_fingerprint: impl AsRef<str>,
) -> bool {
if !mimeparser.e2ee_helper.encrypted {
if 0 == mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, 0, "Message not encrypted.",);
return false;
}

View File

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

View File

@@ -1,5 +1,4 @@
use std::ffi::{CStr, CString};
use std::ptr;
use charset::Charset;
use mmime::mailmime_decode::*;
@@ -80,7 +79,7 @@ fn hex_2_int(ch: libc::c_char) -> libc::c_char {
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur: *const libc::c_char = to_encode;
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if to_encode.is_null() || mmapstr.is_null() {
@@ -271,9 +270,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,
@@ -617,8 +616,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

View File

@@ -14,14 +14,17 @@ pub fn dc_token_save(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
token: &str,
token: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
}
// foreign_id may be 0
sql::execute(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespc as i32, foreign_id as i32, token, time()],
params![namespc as i32, foreign_id as i32, as_str(token), time()],
)
.is_ok()
}
@@ -30,21 +33,33 @@ pub fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> Option<String> {
context.sql.query_row_col::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespc as i32, foreign_id as i32],
0,
)
) -> *mut libc::c_char {
context
.sql
.query_row_col::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespc as i32, foreign_id as i32],
0,
)
.map(|s| unsafe { s.strdup() })
.unwrap_or_else(|| std::ptr::null_mut())
}
pub fn dc_token_exists(context: &Context, namespc: dc_tokennamespc_t, token: &str) -> bool {
pub fn dc_token_exists(
context: &Context,
namespc: dc_tokennamespc_t,
token: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
}
context
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespc as i32, token],
params![namespc as i32, as_str(token)],
)
.unwrap_or_default()
}

View File

@@ -8,14 +8,13 @@ use std::{fmt, fs, ptr};
use chrono::{Local, TimeZone};
use mmime::mailimf_types::*;
use rand::{thread_rng, Rng};
use itertools::max;
use crate::context::Context;
use crate::error::Error;
use crate::types::*;
use crate::x::*;
use itertools::max;
/* Some tools and enhancements to the used libraries, there should be
no references to Context and other "larger" classes here. */
/* ** library-private **********************************************************/
@@ -355,6 +354,33 @@ unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: size_t) -> size_t {
j
}
/* split string into lines*/
pub unsafe fn dc_split_into_lines(buf_terminated: *const libc::c_char) -> Vec<*mut libc::c_char> {
let mut lines = Vec::new();
let mut line_chars = 0;
let mut p1: *const libc::c_char = buf_terminated;
let mut line_start: *const libc::c_char = p1;
while 0 != *p1 {
if *p1 as libc::c_int == '\n' as i32 {
lines.push(strndup(line_start, line_chars));
p1 = p1.offset(1isize);
line_start = p1;
line_chars = 0;
} else {
p1 = p1.offset(1isize);
line_chars += 1;
}
}
lines.push(strndup(line_start, line_chars));
lines
}
pub unsafe fn dc_free_splitted_lines(lines: Vec<*mut libc::c_char>) {
for s in lines {
free(s as *mut libc::c_void);
}
}
pub unsafe fn dc_str_from_clist(
list: *const clist,
delimiter: *const libc::c_char,
@@ -1201,7 +1227,7 @@ impl CStringExt for CString {}
/// Rust strings to raw C strings. This can be clumsy to do correctly
/// and the compiler sometimes allows it in an unsafe way. These
/// methods make it more succinct and help you get it right.
pub trait StrExt {
pub trait Strdup {
/// Allocate a new raw C `*char` version of this string.
///
/// This allocates a new raw C string which must be freed using
@@ -1218,13 +1244,26 @@ pub trait StrExt {
unsafe fn strdup(&self) -> *mut libc::c_char;
}
impl<T: AsRef<str>> StrExt for T {
impl<T: AsRef<str>> Strdup for T {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::yolo(self.as_ref());
dc_strdup(tmp.as_ptr())
}
}
impl Strdup for CStr {
unsafe fn strdup(&self) -> *mut libc::c_char {
dc_strdup(self.as_ptr())
}
}
impl Strdup for std::path::Path {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = self.to_c_string().unwrap();
dc_strdup(tmp.as_ptr())
}
}
pub fn to_string(s: *const libc::c_char) -> String {
if s.is_null() {
return "".into();
@@ -1424,7 +1463,7 @@ mod tests {
);
assert_ne!(str_a, str_a_copy);
let str_a = ptr::null();
let str_a = 0 as *const u8 as *const libc::c_char;
let str_a_copy = dc_strdup_keep_null(str_a);
assert_eq!(str_a.is_null(), true);
assert_eq!(str_a_copy.is_null(), true);

View File

@@ -479,19 +479,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);
}
}
}

View File

@@ -10,11 +10,11 @@ use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_imex::*;
use crate::dc_location::*;
use crate::dc_loginparam::*;
use crate::dc_mimefactory::*;
use crate::dc_tools::*;
use crate::imap::*;
use crate::location;
use crate::message::*;
use crate::param::*;
use crate::sql;
@@ -136,8 +136,12 @@ impl Job {
if unsafe { strlen(filename) } == 0 {
warn!(context, 0, "Missing file name for job {}", self.job_id,);
} else if 0 != unsafe { dc_read_file(context, filename, &mut buf, &mut buf_bytes) } {
if let Some(recipients) = self.param.get(Param::Recipients) {
let recipients = self.param.get(Param::Recipients);
if recipients.is_none() {
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
} else {
let recipients_list = recipients
.unwrap()
.split("\x1e")
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
Ok(addr) => Some(addr),
@@ -206,8 +210,6 @@ impl Job {
}
}
}
} else {
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
}
}
}
@@ -725,7 +727,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
clist_insert_after(
mimefactory.recipients_names,
(*mimefactory.recipients_names).last,
ptr::null_mut(),
0 as *mut libc::c_void,
);
clist_insert_after(
mimefactory.recipients_addr,
@@ -737,19 +739,13 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
}
if 0 != mimefactory.out_last_added_location_id {
if let Err(err) =
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
{
error!(context, 0, "Failed to set kml sent_timestamp: {:?}", err);
}
dc_set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time());
if !mimefactory.msg.hidden {
if let Err(err) = location::set_msg_location_id(
dc_set_msg_location_id(
context,
mimefactory.msg.id,
mimefactory.out_last_added_location_id,
) {
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
}
);
}
}
if 0 != mimefactory.out_encrypted
@@ -879,12 +875,12 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
Action::MaybeSendLocations => {
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
}
Action::MaybeSendLocationsEnded => {
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
}
Action::MaybeSendLocations => unsafe {
dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
},
Action::MaybeSendLocationsEnded => unsafe {
dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
},
Action::Housekeeping => sql::housekeeping(context),
Action::SendMdnOld => {}
Action::SendMsgToSmtpOld => {}
@@ -1018,7 +1014,7 @@ fn send_mdn(context: &Context, msg_id: uint32_t) {
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
let pathNfilename: *mut libc::c_char;
let mut success: libc::c_int = 0i32;
let mut recipients: *mut libc::c_char = ptr::null_mut();
let mut recipients: *mut libc::c_char = 0 as *mut libc::c_char;
let mut param = Params::new();
pathNfilename = unsafe {
dc_get_fine_pathNfilename(

View File

@@ -30,13 +30,11 @@ pub mod configure;
pub mod constants;
pub mod contact;
pub mod context;
mod e2ee;
mod imap;
pub mod job;
mod job_thread;
pub mod key;
pub mod keyring;
pub mod location;
pub mod lot;
pub mod message;
pub mod oauth2;
@@ -52,10 +50,13 @@ pub mod x;
pub mod dc_array;
mod dc_dehtml;
mod dc_e2ee;
pub mod dc_imex;
pub mod dc_location;
mod dc_loginparam;
mod dc_mimefactory;
pub mod dc_mimeparser;
mod dc_move;
pub mod dc_receive_imf;
pub mod dc_securejoin;
mod dc_simplify;

View File

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

View File

@@ -80,7 +80,7 @@ impl Lot {
if msg.state == MessageState::OutDraft {
self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into());
self.text1_meaning = Meaning::Text1Draft;
} else if msg.from_id == DC_CONTACT_ID_SELF {
} else if msg.from_id == DC_CONTACT_ID_SELF as u32 {
if 0 != dc_msg_is_info(msg) || chat.is_self_talk() {
self.text1 = None;
self.text1_meaning = Meaning::None;
@@ -93,7 +93,7 @@ impl Lot {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
if chat.id == DC_CHAT_ID_DEADDROP {
if chat.id == DC_CHAT_ID_DEADDROP as u32 {
if let Some(contact) = contact {
self.text1 = Some(contact.get_display_name().into());
} else {
@@ -208,28 +208,33 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
return ret.strdup();
}
if let Ok(rows) = context.sql.query_map(
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
params![msg_id as i32],
|row| {
let contact_id: i32 = row.get(0)?;
let ts: i64 = row.get(1)?;
Ok((contact_id, ts))
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
) {
for (contact_id, ts) in rows {
let fts = dc_timestamp_to_str(ts);
ret += &format!("Read: {}", fts);
context
.sql
.query_map(
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
params![msg_id as i32],
|row| {
let contact_id: i32 = row.get(0)?;
let ts: i64 = row.get(1)?;
Ok((contact_id, ts))
},
|rows| {
for row in rows {
let (contact_id, ts) = row?;
let fts = dc_timestamp_to_str(ts);
ret += &format!("Read: {}", fts);
let name = Contact::load_from_db(context, contact_id as u32)
.map(|contact| contact.get_name_n_addr())
.unwrap_or_default();
let name = Contact::load_from_db(context, contact_id as u32)
.map(|contact| contact.get_name_n_addr())
.unwrap_or_default();
ret += &format!(" by {}", name);
ret += "\n";
}
}
ret += &format!(" by {}", name);
ret += "\n";
}
Ok(())
},
)
.unwrap(); // TODO: better error handling
ret += "State: ";
use MessageState::*;
@@ -378,7 +383,7 @@ pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)>
}
pub unsafe fn dc_msg_get_file(msg: &Message) -> *mut libc::c_char {
let mut file_abs = ptr::null_mut();
let mut file_abs = 0 as *mut libc::c_char;
if let Some(file_rel) = msg.param.get(Param::File) {
file_abs = dc_get_abs_path(msg.context, file_rel);
@@ -687,7 +692,7 @@ pub unsafe fn dc_msg_get_text(msg: &Message) -> *mut libc::c_char {
#[allow(non_snake_case)]
pub unsafe fn dc_msg_get_filename(msg: &Message) -> *mut libc::c_char {
let mut ret = ptr::null_mut();
let mut ret = 0 as *mut libc::c_char;
if let Some(file) = msg.param.get(Param::File) {
ret = dc_get_filename(file);
@@ -819,21 +824,19 @@ pub fn dc_msg_get_summarytext_by_raw(
}
}
};
if !append_text {
return prefix;
}
if let Some(text) = text {
if prefix.is_empty() {
dc_truncate(text.as_ref(), approx_characters, true).to_string()
} else {
let ret = if append_text && text.is_some() {
let text = text.unwrap();
if !prefix.is_empty() {
let tmp = format!("{} {}", prefix, text.as_ref());
dc_truncate(&tmp, approx_characters, true).to_string()
} else {
dc_truncate(text.as_ref(), approx_characters, true).to_string()
}
} else {
prefix
}
};
ret
}
pub unsafe fn dc_msg_has_deviating_timestamp(msg: &Message) -> libc::c_int {
@@ -897,14 +900,14 @@ pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
}
pub unsafe fn dc_msg_get_setupcodebegin(msg: &Message) -> *mut libc::c_char {
let mut filename: *mut libc::c_char = ptr::null_mut();
let mut buf: *mut libc::c_char = ptr::null_mut();
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
let mut buf: *mut libc::c_char = 0 as *mut libc::c_char;
let mut buf_bytes: size_t = 0i32 as size_t;
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_headerline: *const libc::c_char = ptr::null();
let mut buf_headerline: *const libc::c_char = 0 as *const libc::c_char;
// just a pointer inside buf, MUST NOT be free()'d
let mut buf_setupcodebegin: *const libc::c_char = ptr::null();
let mut ret: *mut libc::c_char = ptr::null_mut();
let mut buf_setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
let mut ret: *mut libc::c_char = 0 as *mut libc::c_char;
if dc_msg_is_setupmessage(msg) {
filename = dc_msg_get_file(msg);
if !(filename.is_null() || *filename.offset(0isize) as libc::c_int == 0i32) {
@@ -922,8 +925,8 @@ pub unsafe fn dc_msg_get_setupcodebegin(msg: &Message) -> *mut libc::c_char {
buf,
&mut buf_headerline,
&mut buf_setupcodebegin,
ptr::null_mut(),
ptr::null_mut(),
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
) && strcmp(
buf_headerline,
b"-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char,
@@ -1265,7 +1268,7 @@ pub fn dc_rfc724_mid_exists(
Ok(res) => res,
Err(_err) => {
if !ret_server_folder.is_null() {
unsafe { *ret_server_folder = ptr::null_mut() };
unsafe { *ret_server_folder = 0 as *mut libc::c_char };
}
if !ret_server_uid.is_null() {
unsafe { *ret_server_uid = 0 };

View File

@@ -33,7 +33,7 @@ pub unsafe fn dc_split_armored_data(
let mut headerline: *mut libc::c_char = ptr::null_mut();
let mut base64: *mut libc::c_char = ptr::null_mut();
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();

View File

@@ -38,16 +38,13 @@ impl Sql {
info!(context, 0, "Database closed.");
}
// return true on success, false on failure
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
match open(context, self, dbfile, flags) {
Ok(_) => true,
Err(Error::SqlAlreadyOpen) => false,
Err(_) => {
self.close(context);
false
}
}
pub fn open(
&self,
context: &Context,
dbfile: &std::path::Path,
flags: libc::c_int,
) -> Result<()> {
open(context, self, dbfile, flags)
}
pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize>
@@ -227,7 +224,10 @@ impl Sql {
match res {
Ok(_) => Ok(()),
Err(err) => {
error!(context, 0, "set_config(): Cannot change value. {:?}", &err);
error!(
context,
0, "set_config(): Cannot change value for {}: {:?}", key, &err
);
Err(err.into())
}
}

View File

@@ -232,10 +232,7 @@ 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;
@@ -253,19 +250,18 @@ mod tests {
#[test]
fn test_stock_str() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
let t = dummy_context();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
}
unsafe extern "C" fn test_stock_str_no_fallback_cb(
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
unsafe { "Hello there".strdup() as usize }
} else {
0
}
@@ -273,16 +269,16 @@ mod tests {
#[test]
fn test_stock_str_no_fallback() {
let t = test_context(Some(test_stock_str_no_fallback_cb));
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
}
#[test]
fn test_stock_string_repl_str() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
// uses %1$s substitution
assert_eq!(
ctx.stock_string_repl_str(StockMessage::Member, "42"),
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
"42 member(s)"
);
// We have no string using %1$d to test...
@@ -290,40 +286,42 @@ mod tests {
#[test]
fn test_stock_string_repl_int() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_string_repl_int(StockMessage::Member, 42),
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
"42 member(s)"
);
}
#[test]
fn test_stock_string_repl_str2() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
t.ctx
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
"Response from foo: bar"
);
}
#[test]
fn test_stock_system_msg_simple() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
t.ctx
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
"Location streaming enabled."
)
}
#[test]
fn test_stock_system_msg_add_member_by_me() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_system_msg(
t.ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",
DC_CONTACT_ID_SELF
DC_CONTACT_ID_SELF as u32
),
"Member alice@example.com added by me."
)
@@ -338,7 +336,7 @@ mod tests {
StockMessage::MsgAddMember,
"alice@example.com",
"",
DC_CONTACT_ID_SELF
DC_CONTACT_ID_SELF as u32
),
"Member Alice (alice@example.com) added by me."
);
@@ -373,7 +371,7 @@ mod tests {
StockMessage::MsgGrpName,
"Some chat",
"Other chat",
DC_CONTACT_ID_SELF
DC_CONTACT_ID_SELF as u32
),
"Group name changed from \"Some chat\" to \"Other chat\" by me."
)

View File

@@ -9,9 +9,8 @@ use tempfile::{tempdir, TempDir};
use crate::config::Config;
use crate::constants::{Event, KeyType};
use crate::context::{dc_context_new, dc_open, Context};
use crate::context::{Context, ContextCallback};
use crate::key;
use crate::types::dc_callback_t;
/// A Context and temporary directory.
///
@@ -28,18 +27,15 @@ pub struct TestContext {
/// "db.sqlite" in the [TestContext.dir] directory.
///
/// [Context]: crate::context::Context
pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
unsafe {
let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
"Failed to open {}",
dbfile.display(),
);
TestContext { ctx: ctx, dir: dir }
}
pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let cb: Box<ContextCallback> = match callback {
Some(cb) => cb,
None => Box::new(|_, _, _, _| 0),
};
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
}
/// Return a dummy [TestContext].
@@ -51,13 +47,8 @@ pub fn dummy_context() -> TestContext {
test_context(None)
}
pub unsafe extern "C" fn logging_cb(
_ctx: &Context,
evt: Event,
_d1: uintptr_t,
d2: uintptr_t,
) -> uintptr_t {
let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap();
pub fn logging_cb(_ctx: &Context, evt: Event, _d1: uintptr_t, d2: uintptr_t) -> uintptr_t {
let to_str = unsafe { |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)),

View File

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

View File

@@ -1,21 +1,9 @@
#![allow(non_camel_case_types)]
use crate::constants::Event;
use crate::context::Context;
pub use mmime::clist::*;
pub use rusqlite::ffi::*;
/// Callback function that should be given to dc_context_new().
///
/// @memberof Context
/// @param context The context object as returned by dc_context_new().
/// @param event one of the @ref DC_EVENT constants
/// @param data1 depends on the event parameter
/// @param data2 depends on the event parameter
/// @return return 0 unless stated otherwise in the event parameter documentation
pub type dc_callback_t =
unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t;
pub type dc_receive_imf_t = unsafe fn(
_: &Context,
_: *const libc::c_char,

View File

@@ -2,8 +2,8 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use mmime::mailimf_types::*;
use tempfile::{tempdir, TempDir};
use deltachat::chat::{self, Chat};
@@ -12,6 +12,7 @@ use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_mimeparser::*;
use deltachat::dc_tools::*;
use deltachat::key::*;
use deltachat::keyring::*;
@@ -32,103 +33,100 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
as *const u8 as *const libc::c_char;
unsafe fn stress_functions(context: &Context) {
if 0 != dc_is_open(context) {
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
dc_write_file(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(
dc_get_filebytes(context, "$BLOBDIR/foobar",),
7i32 as libc::c_ulonglong
);
let abs_path: *mut libc::c_char = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
b"foobar\x00" as *const u8 as *const libc::c_char,
);
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
assert!(dc_file_exist(context, as_path(abs_path)));
free(abs_path as *mut libc::c_void);
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let mut buf: *mut libc::c_void = ptr::null_mut();
let mut buf_bytes: size_t = 0;
assert_eq!(
dc_read_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
&mut buf,
&mut buf_bytes,
),
1
);
assert_eq!(buf_bytes, 7);
assert_eq!(
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
"content"
);
free(buf as *mut _);
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn0.is_null());
assert_eq!(
strcmp(
fn0,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
dc_write_file(
context,
fn0,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn1.is_null());
assert_eq!(
strcmp(
fn1,
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(dc_delete_file(context, as_path(fn0)));
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
if dc_file_exist(context, "$BLOBDIR/foobar")
|| dc_file_exist(context, "$BLOBDIR/dada")
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
{
dc_delete_file(context, "$BLOBDIR/foobar");
dc_delete_file(context, "$BLOBDIR/dada");
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
dc_delete_file(context, "$BLOBDIR/foobar-folder");
}
dc_write_file(
context,
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
assert_eq!(
dc_get_filebytes(context, "$BLOBDIR/foobar",),
7i32 as libc::c_ulonglong
);
let abs_path: *mut libc::c_char = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
b"foobar\x00" as *const u8 as *const libc::c_char,
);
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo"));
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo"));
assert!(dc_file_exist(context, as_path(abs_path)));
free(abs_path as *mut libc::c_void);
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let mut buf: *mut libc::c_void = 0 as *mut libc::c_void;
let mut buf_bytes: size_t = 0;
assert_eq!(
dc_read_file(
context,
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
&mut buf,
&mut buf_bytes,
),
1
);
assert_eq!(buf_bytes, 7);
assert_eq!(
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
"content"
);
free(buf as *mut _);
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn0.is_null());
assert_eq!(
strcmp(
fn0,
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
dc_write_file(
context,
fn0,
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
7i32 as size_t,
);
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
);
assert!(!fn1.is_null());
assert_eq!(
strcmp(
fn1,
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(dc_delete_file(context, as_path(fn0)));
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
@@ -162,10 +160,10 @@ unsafe fn stress_functions(context: &Context) {
assert!(res.contains(" configured_server_flags "));
let mut buf_0: *mut libc::c_char;
let mut headerline: *const libc::c_char = ptr::null();
let mut setupcodebegin: *const libc::c_char = ptr::null();
let mut preferencrypt: *const libc::c_char = ptr::null();
let mut base64: *const libc::c_char = ptr::null();
let mut headerline: *const libc::c_char = 0 as *const libc::c_char;
let mut setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
let mut preferencrypt: *const libc::c_char = 0 as *const libc::c_char;
let mut base64: *const libc::c_char = 0 as *const libc::c_char;
buf_0 = strdup(
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\ndata\n-----END PGP MESSAGE-----\x00" as *const u8
as *const libc::c_char,
@@ -174,7 +172,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
assert!(ok);
@@ -199,7 +197,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
@@ -226,7 +224,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
@@ -251,7 +249,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
@@ -264,7 +262,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0,
&mut headerline,
&mut setupcodebegin,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut base64,
);
assert!(ok);
@@ -297,7 +295,7 @@ unsafe fn stress_functions(context: &Context) {
let ok = dc_split_armored_data(
buf_0,
&mut headerline,
ptr::null_mut(),
0 as *mut *const libc::c_char,
&mut preferencrypt,
&mut base64,
);
@@ -353,16 +351,16 @@ unsafe fn stress_functions(context: &Context) {
);
free(norm as *mut libc::c_void);
let mut buf_1: *mut libc::c_char;
let mut headerline_0: *const libc::c_char = ptr::null();
let mut setupcodebegin_0: *const libc::c_char = ptr::null();
let mut preferencrypt_0: *const libc::c_char = ptr::null();
let mut headerline_0: *const libc::c_char = 0 as *const libc::c_char;
let mut setupcodebegin_0: *const libc::c_char = 0 as *const libc::c_char;
let mut preferencrypt_0: *const libc::c_char = 0 as *const libc::c_char;
buf_1 = strdup(S_EM_SETUPFILE);
assert!(dc_split_armored_data(
buf_1,
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
ptr::null_mut(),
0 as *mut *const libc::c_char,
));
assert!(!headerline_0.is_null());
assert_eq!(
@@ -388,7 +386,7 @@ unsafe fn stress_functions(context: &Context) {
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
ptr::null_mut(),
0 as *mut *const libc::c_char,
));
assert!(!headerline_0.is_null());
assert_eq!(
@@ -416,16 +414,16 @@ unsafe fn stress_functions(context: &Context) {
// let setupcode_c = CString::yolo(setupcode.clone());
// let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
// let setupfile_c = CString::yolo(setupfile);
// let mut headerline_2: *const libc::c_char = ptr::null();
// let mut headerline_2: *const libc::c_char = 0 as *const libc::c_char;
// let payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
// assert!(payload.is_null());
// assert!(!dc_split_armored_data(
// payload,
// &mut headerline_2,
// ptr::null_mut(),
// ptr::null_mut(),
// ptr::null_mut(),
// 0 as *mut *const libc::c_char,
// 0 as *mut *const libc::c_char,
// 0 as *mut *const libc::c_char,
// ));
// assert!(!headerline_2.is_null());
// assert_eq!(
@@ -625,12 +623,7 @@ fn test_encryption_decryption() {
}
}
unsafe extern "C" fn cb(
_context: &Context,
_event: Event,
_data1: uintptr_t,
_data2: uintptr_t,
) -> uintptr_t {
fn cb(_context: &Context, _event: Event, _data1: uintptr_t, _data2: uintptr_t) -> uintptr_t {
0
}
@@ -640,21 +633,47 @@ struct TestContext {
dir: TempDir,
}
unsafe fn create_test_context() -> TestContext {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
fn create_test_context() -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
"Failed to open {}",
dbfile.display()
);
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
}
#[test]
fn test_dc_mimeparser_with_context() {
unsafe {
let context = create_test_context();
let mut mimeparser = dc_mimeparser_new(&context.ctx);
let raw: *const libc::c_char =
b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00"
as *const u8 as *const libc::c_char;
dc_mimeparser_parse(&mut mimeparser, raw, strlen(raw));
assert_eq!(
as_str(mimeparser.subject as *const libc::c_char),
"inner-subject",
);
let mut of: *mut mailimf_optional_field =
dc_mimeparser_lookup_optional_field(&mimeparser, "X-Special-A");
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "special-a",);
of = dc_mimeparser_lookup_optional_field(&mimeparser, "Foo");
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "Bar",);
of = dc_mimeparser_lookup_optional_field(&mimeparser, "Chat-Version");
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "1.0",);
assert_eq!(mimeparser.parts.len(), 1);
dc_mimeparser_unref(&mut mimeparser);
}
}
#[test]
fn test_dc_get_oauth2_url() {
let ctx = unsafe { create_test_context() };
let ctx = create_test_context();
let addr = "dignifiedquire@gmail.com";
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
@@ -664,7 +683,7 @@ fn test_dc_get_oauth2_url() {
#[test]
fn test_dc_get_oauth2_addr() {
let ctx = unsafe { create_test_context() };
let ctx = create_test_context();
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
@@ -674,7 +693,7 @@ fn test_dc_get_oauth2_addr() {
#[test]
fn test_dc_get_oauth2_token() {
let ctx = unsafe { create_test_context() };
let ctx = create_test_context();
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
@@ -692,20 +711,18 @@ fn test_stress_tests() {
#[test]
fn test_get_contacts() {
unsafe {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(id, 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(contacts.len(), 0);
}
#[test]
@@ -726,16 +743,3 @@ fn test_chat() {
assert_eq!(chat2.name, chat.name);
}
}
#[test]
fn test_wrong_db() {
unsafe {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
std::fs::write(&dbfile, b"123").unwrap();
let res = dc_open(&mut ctx, dbfile.to_str().unwrap(), None);
assert!(!res);
}
}