Compare commits

..

14 Commits

Author SHA1 Message Date
holger krekel
6e16ea5f8b intermediate commit 2019-08-23 13:30:09 +02:00
holger krekel
2fd4b09cbc rustify several things around the precheck function 2019-08-22 21:49:47 +02:00
holger krekel
157d68246f Merge branch 'master' into cleanup/jobsend 2019-08-21 09:34:46 +02:00
holger krekel
6b044dd85a make python tests run the mvbox thread 2019-08-21 07:39:41 +02:00
holger krekel
9227c4b55c - simplify inbox-connection-guards and returns
- make dc_msg_exists safe
2019-08-20 11:15:28 +02:00
holger krekel
287ccb15ba address @dignifiedquire Default suggestion 2019-08-20 10:40:27 +02:00
holger krekel
f47e637519 cargo fmt 2019-08-20 09:31:05 +02:00
holger krekel
9633f251e0 deindent/cleanup set_mdnsent and delete_msg return code handling 2019-08-20 09:26:57 +02:00
holger krekel
e319f51342 Merge branch 'master' into cleanup/jobsend 2019-08-20 09:08:21 +02:00
holger krekel
55b62b0b7d cargo fmt 2019-08-20 09:03:36 +02:00
holger krekel
37d20bfb5c select_folder -> bool 2019-08-20 08:59:33 +02:00
holger krekel
6441ceeedc streamline imap mv() wrt to return codes, follow C more closely and add comments about missing dest_uid setting from imap 2019-08-20 08:36:27 +02:00
holger krekel
7641bb4d9a implement ImapResult enum like it was in C, and simplify logic a bit (with @r10s on the side) 2019-08-19 23:21:54 +02:00
holger krekel
67594b52f9 rustify job send 2019-08-19 22:13:49 +02:00
48 changed files with 3665 additions and 9307 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

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 */
@@ -527,17 +526,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 +652,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 +778,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 +819,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 +834,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 +842,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 +868,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,6 @@ extern crate lazy_static;
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
@@ -385,7 +384,11 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
let mut context = dc_context_new(
Some(receive_event),
0 as *mut libc::c_void,
Some("CLI".into()),
);
unsafe { dc_cmdline_skip_auth() };

View File

@@ -377,12 +377,15 @@ class IOThreads:
def is_started(self):
return len(self._name2thread) > 0
def start(self, imap=True, smtp=True):
def start(self, imap=True, smtp=True, mvbox=True):
assert not self.is_started()
if imap:
self._start_one_thread("imap", self.imap_thread_run)
self._start_one_thread("inbox", self.inbox_thread_run)
if smtp:
self._start_one_thread("smtp", self.smtp_thread_run)
if mvbox:
self._start_one_thread("mvbox", self.mvbox_thread_run)
def _start_one_thread(self, name, func):
self._name2thread[name] = t = threading.Thread(target=func, name=name)
@@ -397,13 +400,20 @@ class IOThreads:
for name, thread in self._name2thread.items():
thread.join()
def imap_thread_run(self):
self._log_event("py-bindings-info", 0, "IMAP THREAD START")
def inbox_thread_run(self):
self._log_event("py-bindings-info", 0, "INBOX IMAP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_imap_jobs(self._dc_context)
lib.dc_perform_imap_fetch(self._dc_context)
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
self._log_event("py-bindings-info", 0, "INBOX IMAP THREAD FINISHED")
def mvbox_thread_run(self):
self._log_event("py-bindings-info", 0, "MVBOX IMAP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_mvbox_fetch(self._dc_context)
lib.dc_perform_mvbox_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "MVBOX IMAP THREAD FINISHED")
def smtp_thread_run(self):
self._log_event("py-bindings-info", 0, "SMTP THREAD START")

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,7 +1025,7 @@ 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;
@@ -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,8 +106,8 @@ impl Default for Chattype {
}
}
pub const DC_MSG_ID_MARKER1: u32 = 1;
const DC_MSG_ID_DAYMARKER: u32 = 9;
pub const DC_MSG_ID_MARKER1: usize = 1;
const DC_MSG_ID_DAYMARKER: usize = 9;
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
/// approx. max. length returned by dc_msg_get_text()
@@ -115,13 +115,22 @@ 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;
#[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum Delay {
Default = 0,
AtOnce = -1,
Standard = 3,
IncreationPoll = 2,
}
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
// and may be set together with the username, password etc.

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

@@ -4,6 +4,7 @@ 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::imap::*;
@@ -192,43 +193,41 @@ unsafe fn cb_precheck_imf(
server_folder: &str,
server_uid: uint32_t,
) -> libc::c_int {
let mut rfc724_mid_exists: libc::c_int = 0i32;
let msg_id: uint32_t;
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
let mut old_server_uid: uint32_t = 0i32 as uint32_t;
let mut mark_seen: libc::c_int = 0i32;
msg_id = dc_rfc724_mid_exists(
let rfc724_mid = as_str(rfc724_mid)
.trim_start_matches("<")
.trim_end_matches(">");
let mut mark_seen = false;
info!(
context,
0,
"cb_precheck_imf rfc724_mid={:?}, server_folder={}, server_uid={}",
rfc724_mid,
&mut old_server_folder,
&mut old_server_uid,
server_folder,
server_uid
);
if msg_id != 0i32 as libc::c_uint {
rfc724_mid_exists = 1i32;
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
&& old_server_uid == 0i32 as libc::c_uint
{
info!(
context,
0,
"[move] detected bbc-self {}",
as_str(rfc724_mid),
);
mark_seen = 1i32
} else if as_str(old_server_folder) != server_folder {
info!(
context,
0,
"[move] detected moved message {}",
as_str(rfc724_mid),
);
if let Some(res) = dc_rfc724_mid_exists_with_msg_state(context, rfc724_mid) {
let (msg_id, _, old_server_folder, old_server_uid) = res;
info!(context, 0, "precheck msg_id={}", msg_id);
if msg_id != 0 {
if old_server_folder.is_empty() {
info!(context, 0, "[move] detected bbc-self {}", rfc724_mid,);
mark_seen = true;
} else if old_server_folder != server_folder {
info!(context, 0, "[move] detected moved message {}", rfc724_mid,);
}
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
if old_server_folder != server_folder || old_server_uid != server_uid {
info!(
context,
0, "precheck updated server_uid for msg_id={}", msg_id
);
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
job_retry_msg_actions_at_once(context, msg_id);
}
do_heuristics_moves(context, server_folder, msg_id);
if 0 != mark_seen {
dc_do_heuristics_moves(context, server_folder, msg_id);
if mark_seen {
job_add(
context,
Action::MarkseenMsgOnImap,
@@ -237,9 +236,9 @@ unsafe fn cb_precheck_imf(
0,
);
}
return msg_id as i32;
}
free(old_server_folder as *mut libc::c_void);
rfc724_mid_exists
0
}
fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
@@ -299,11 +298,9 @@ pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) ->
if 0 != dc_is_open(context) {
return false;
}
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
let blobdir = blobdir.unwrap_or_default();
if !blobdir.is_empty() {
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
if blobdir.is_some() && !blobdir.unwrap().is_empty() {
let dir = dc_ensure_no_slash_safe(blobdir.unwrap()).strdup();
*context.blobdir.write().unwrap() = dir;
} else {
let dir = dbfile.to_string() + "-blobs";
@@ -574,45 +571,6 @@ 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::*;

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::*;
@@ -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,
@@ -712,7 +712,7 @@ The macro avoids weird values of 0% or 100% while still working. */
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> 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;
@@ -768,30 +768,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 +824,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 +869,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 +881,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 +943,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 +991,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 +1036,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),
);
}
}

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

45
src/dc_move.rs Normal file
View File

@@ -0,0 +1,45 @@
use crate::constants::*;
use crate::context::*;
use crate::dc_tools::as_str;
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, as_str(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, as_str(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,
@@ -342,26 +343,10 @@ unsafe fn add_parts(
// check, if the mail is already in our database - if so, just update the folder/uid
// (if the mail was moved around) and finish. (we may get a mail twice eg. if it is
// moved between folders. make sure, this check is done eg. before securejoin-processing) */
let mut old_server_folder = std::ptr::null_mut();
let mut old_server_uid = 0;
if 0 != dc_rfc724_mid_exists(
context,
rfc724_mid,
&mut old_server_folder,
&mut old_server_uid,
) {
if as_str(old_server_folder) != server_folder.as_ref() || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder.as_ref(), server_uid);
}
free(old_server_folder.cast());
cleanup(mime_in_reply_to, mime_references, txt_raw);
bail!("Message already in DB");
}
// XXX call precheck?
// 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 +429,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 +476,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 +493,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 +549,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 +614,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 +657,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 +687,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 +714,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 +726,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 +788,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 +798,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 +892,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 +922,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 +1244,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 +1283,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 +1327,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 +1336,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 +1381,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 +1391,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 +1419,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 +1463,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 +1479,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 +1755,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 +1846,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 +1869,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 +1881,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 +1892,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 +1979,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 +2023,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 +2054,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 +2074,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 +2089,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

@@ -355,6 +355,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,
@@ -1424,7 +1451,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

@@ -12,14 +12,19 @@ use crate::dc_loginparam::*;
use crate::dc_tools::CStringExt;
use crate::oauth2::dc_get_oauth2_access_token;
use crate::types::*;
use deltachat_derive::*;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
const DC_SUCCESS: usize = 3;
const DC_ALREADY_DONE: usize = 2;
const DC_RETRY_LATER: usize = 1;
const DC_FAILED: usize = 0;
#[repr(usize)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
pub enum ImapResult {
Success = 3,
AlreadyDone = 2,
RetryLater = 1,
Failed = 0,
}
const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
@@ -479,19 +484,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);
}
}
}
@@ -626,26 +637,30 @@ impl Imap {
}
}
fn select_folder<S: AsRef<str>>(&self, context: &Context, folder: Option<S>) -> usize {
fn select_folder<S: AsRef<str>>(&self, context: &Context, folder: Option<S>) -> bool {
info!(context, 0, "select_folder0");
if self.session.lock().unwrap().is_none() {
// we are in termination, noting useful to be done anymore
let mut cfg = self.config.write().unwrap();
cfg.selected_folder = None;
cfg.selected_folder_needs_expunge = false;
return 0;
return false;
}
info!(context, 0, "select_folder1");
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
// if there is _no_ new folder, we continue as we might want to expunge below.
if let Some(ref folder) = folder {
if let Some(ref selected_folder) = self.config.read().unwrap().selected_folder {
if folder.as_ref() == selected_folder {
return 1;
return true;
}
}
}
info!(context, 0, "select_folder2");
// deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then)
if self.config.read().unwrap().selected_folder_needs_expunge {
info!(context, 0, "select_folder3");
if let Some(ref folder) = self.config.read().unwrap().selected_folder {
info!(context, 0, "Expunge messages in \"{}\".", folder);
@@ -659,22 +674,26 @@ impl Imap {
}
}
} else {
return 0;
unreachable!();
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
self.config.write().unwrap().selected_folder_needs_expunge = false;
}
}
info!(context, 0, "select_folder4");
// select new folder
if let Some(ref folder) = folder {
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
info!(context, 0, "select_folder5");
match session.select(folder) {
Ok(mailbox) => {
info!(context, 0, "select_folder6");
let mut config = self.config.write().unwrap();
config.selected_folder = Some(folder.as_ref().to_string());
config.selected_mailbox = Some(mailbox);
}
Err(err) => {
info!(context, 0, "select_folder7");
info!(
context,
0,
@@ -685,15 +704,14 @@ impl Imap {
self.config.write().unwrap().selected_folder = None;
self.should_reconnect.store(true, Ordering::Relaxed);
return 0;
return false;
}
}
} else {
return 0;
unreachable!();
}
}
1
true
}
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
@@ -722,7 +740,7 @@ impl Imap {
return 0;
}
if self.select_folder(context, Some(&folder)) == 0 {
if !self.select_folder(context, Some(&folder)) {
info!(
context,
0,
@@ -1000,7 +1018,7 @@ impl Imap {
self.setup_handle_if_needed(context);
let watch_folder = self.config.read().unwrap().watch_folder.clone();
if self.select_folder(context, watch_folder.as_ref()) == 0 {
if !self.select_folder(context, watch_folder.as_ref()) {
warn!(context, 0, "IMAP-IDLE not setup.",);
return self.fake_idle(context);
@@ -1028,14 +1046,17 @@ impl Imap {
// if needed, the ui can call dc_imap_interrupt_idle() to trigger a reconnect.
idle.set_keepalive(Duration::from_secs(23 * 60));
let res = idle.wait_keepalive();
eprintln!("idle wait_keepalive returned");
// Ignoring the error, as this happens when we try sending after the drop
let _send_res = sender.send(res);
eprintln!("idle sending result");
// Trigger condvar
let mut watch = lock.lock().unwrap();
*watch = true;
cvar.notify_one();
eprintln!("idle spawn thread ending");
}
});
receiver
@@ -1158,12 +1179,9 @@ impl Imap {
uid: u32,
dest_folder: S2,
dest_uid: &mut u32,
) -> usize {
let mut res = DC_RETRY_LATER;
let set = format!("{}", uid);
) -> ImapResult {
if uid == 0 {
res = DC_FAILED;
return ImapResult::Failed;
} else if folder.as_ref() == dest_folder.as_ref() {
info!(
context,
@@ -1173,101 +1191,85 @@ impl Imap {
uid,
dest_folder.as_ref()
);
return ImapResult::AlreadyDone;
}
info!(
context,
0,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
);
res = DC_ALREADY_DONE;
} else {
info!(
if !self.select_folder(context, Some(folder.as_ref())) {
warn!(
context,
0,
"Moving message {}/{} to {}...",
folder.as_ref(),
uid,
dest_folder.as_ref()
"Cannot select folder {} for moving message.",
folder.as_ref()
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
0,
"Cannot select folder {} for moving message.",
folder.as_ref()
);
return if !self.should_reconnect() {
ImapResult::Failed
} else {
let moved = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
res = DC_SUCCESS;
true
}
Err(err) => {
info!(
context,
0,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
dest_folder.as_ref(),
err
);
false
}
}
} else {
unreachable!();
};
if !moved {
let copied = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_copy(&set, &dest_folder) {
Ok(_) => true,
Err(err) => {
eprintln!("error copy: {:?}", err);
info!(context, 0, "Cannot copy message.",);
false
}
}
} else {
unreachable!();
};
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, 0, "Cannot mark message as \"Deleted\".",);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
res = DC_SUCCESS;
}
ImapResult::RetryLater
};
}
let set = format!("{}", uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_mv(&set, &dest_folder) {
Ok(_) => {
*dest_uid = 0;
return ImapResult::Success;
}
Err(err) => {
info!(
context,
0,
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
folder.as_ref(),
uid,
dest_folder.as_ref(),
err
);
}
}
}
} else {
unreachable!();
};
if res == DC_SUCCESS {
// TODO: is this correct?
*dest_uid = uid;
}
if res == DC_RETRY_LATER {
if self.should_reconnect() {
DC_RETRY_LATER
} else {
DC_FAILED
// message was NOT moved, let's try copy
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_copy(&set, &dest_folder) {
Ok(_) => {}
Err(err) => {
info!(context, 0, "Cannot copy message. {:?}", err);
return ImapResult::Failed;
}
}
} else {
res
unreachable!();
};
if self.add_flag(context, uid, "\\Deleted") {
self.config.write().unwrap().selected_folder_needs_expunge = true;
return ImapResult::Success;
}
warn!(context, 0, "Cannot mark message as \"Deleted\".",);
return ImapResult::Failed;
}
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> bool {
if server_uid == 0 {
return 0;
return false;
}
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
let set = format!("{}", server_uid);
let query = format!("+FLAGS ({})", flag.as_ref());
match session.uid_store(&set, &query) {
Ok(_) => {}
Ok(_) => {
return true;
}
Err(err) => {
warn!(
context,
@@ -1276,22 +1278,21 @@ impl Imap {
}
}
}
// All non-connection states are treated as success - the mail may
// already be deleted or moved away on the server.
if self.should_reconnect() {
0
} else {
1
}
false
}
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> usize {
let mut res = DC_RETRY_LATER;
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
if uid == 0 {
res = DC_FAILED
} else if self.is_connected() {
info!(
context,
0,
"set_seen folder={} uid={}",
folder.as_ref(),
uid
);
return ImapResult::Failed;
}
if self.is_connected() {
info!(
context,
0,
@@ -1300,226 +1301,210 @@ impl Imap {
uid,
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
if !self.select_folder(context, Some(folder.as_ref())) {
warn!(
context,
0,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
} else if !self.add_flag(context, uid, "\\Seen") {
warn!(context, 0, "Cannot mark message as seen.",);
} else {
res = DC_SUCCESS
}
}
if res == DC_RETRY_LATER {
if self.should_reconnect() {
DC_RETRY_LATER
} else {
DC_FAILED
}
} else {
res
}
}
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> usize {
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
let mut res = DC_RETRY_LATER;
let set = format!("{}", uid);
if uid == 0 {
res = DC_FAILED;
} else if self.is_connected() {
info!(
context,
0,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
info!(
context,
0,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
"success marking message {}/{} as seen...",
folder.as_ref(),
uid,
);
} else {
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.expect("just selected folder");
if can_create_flag {
let fetched_msgs =
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, FETCH_FLAGS) {
Ok(res) => Some(res),
Err(err) => {
eprintln!("fetch error: {:?}", err);
None
}
}
} else {
unreachable!();
};
if let Some(msgs) = fetched_msgs {
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
res = if flag_set {
DC_ALREADY_DONE
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
DC_SUCCESS
} else {
res
};
if res == DC_SUCCESS {
info!(context, 0, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, 0, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = DC_SUCCESS;
info!(
context,
0, "Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
}
return ImapResult::Success;
}
}
if res == DC_RETRY_LATER {
if self.should_reconnect() {
DC_RETRY_LATER
} else {
DC_FAILED
}
if self.should_reconnect() {
ImapResult::RetryLater
} else {
res
ImapResult::Failed
}
}
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
if uid == 0 {
return ImapResult::Failed;
}
if !self.is_connected() {
return ImapResult::RetryLater;
}
info!(
context,
0,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
);
if !self.select_folder(context, Some(folder.as_ref())) {
warn!(
context,
0,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
);
return ImapResult::Failed;
}
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.expect("just selected folder");
if !can_create_flag {
info!(
context,
0, "Cannot store $MDNSent flags, ignoring to prevent duplicate MDN.",
);
return ImapResult::Success;
}
let set = format!("{}", uid);
let fetched_msgs = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, FETCH_FLAGS) {
Ok(res) => res,
Err(err) => {
eprintln!("fetch error: {:?}", err);
return ImapResult::Failed;
}
}
} else {
unreachable!();
};
let flag_set = fetched_msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
if flag_set {
info!(context, 0, "$MDNSent already set and MDN already sent.");
ImapResult::AlreadyDone
} else if self.add_flag(context, uid, "$MDNSent") {
info!(context, 0, "$MDNSent just set and MDN will be sent.");
ImapResult::Success
} else if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
}
// only returns 0 on connection problems; we should try later again in this case *
pub fn delete_msg<S1: AsRef<str>, S2: AsRef<str>>(
&self,
context: &Context,
message_id: S1,
folder: S2,
server_uid: &mut u32,
) -> usize {
let mut success = false;
) -> ImapResult {
if *server_uid == 0 {
success = true
} else {
info!(
return ImapResult::Failed;
}
if !self.is_connected() {
return ImapResult::RetryLater;
}
info!(
context,
0,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
server_uid,
);
if !self.select_folder(context, Some(&folder)) {
warn!(
context,
0,
"Marking message \"{}\", {}/{} for deletion...",
message_id.as_ref(),
folder.as_ref(),
server_uid,
"Cannot select folder {} for deleting message.",
folder.as_ref()
);
if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
0,
"Cannot select folder {} for deleting message.",
folder.as_ref()
);
} else {
let set = format!("{}", server_uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(msgs) => {
if msgs.is_empty()
|| msgs
.first()
.unwrap()
.envelope()
.expect("missing envelope")
.message_id
.expect("missing message id")
!= message_id.as_ref()
{
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
message_id.as_ref(),
);
*server_uid = 0;
}
}
Err(err) => {
eprintln!("fetch error: {:?}", err);
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} not found.",
folder.as_ref(),
server_uid,
);
*server_uid = 0;
}
return ImapResult::RetryLater;
}
let set = format!("{}", server_uid);
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, PREFETCH_FLAGS) {
Ok(msgs) => {
if msgs.is_empty()
|| msgs
.first()
.unwrap()
.envelope()
.expect("missing envelope")
.message_id
.expect("missing message id")
!= message_id.as_ref()
{
warn!(
context,
0,
"Cannot delete on IMAP, {}/{} does not match {}.",
folder.as_ref(),
server_uid,
message_id.as_ref(),
);
*server_uid = 0;
return ImapResult::Failed;
}
}
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
warn!(context, 0, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;
success = true
Err(err) => {
warn!(
context,
0,
"Cannot delete on IMAP, {}/{}, fetch error {:?}.",
folder.as_ref(),
server_uid,
err
);
*server_uid = 0;
return ImapResult::Failed;
}
}
}
if success {
1
// mark the message for deletion
if !self.add_flag(context, *server_uid, "\\Deleted") {
warn!(context, 0, "Cannot mark message as \"Deleted\".");
*server_uid = 0;
ImapResult::Failed
} else {
self.is_connected() as usize
self.config.write().unwrap().selected_folder_needs_expunge = true;
ImapResult::Success
}
}

View File

@@ -1,5 +1,4 @@
use std::ffi::CStr;
use std::ptr;
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
@@ -10,11 +9,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;
@@ -82,7 +81,7 @@ pub struct Job {
pub added_timestamp: i64,
pub tries: i32,
pub param: Params,
pub try_again: i32,
pub try_again: Delay,
pub pending_error: Option<String>,
}
@@ -111,165 +110,132 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_SEND(&mut self, context: &Context) {
let ok_to_continue;
let mut filename = ptr::null_mut();
let mut buf = ptr::null_mut();
let mut buf_bytes = 0;
/* connect to SMTP server, if not yet done */
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = dc_loginparam_read(context, &context.sql, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if !connected {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
self.try_again_later(Delay::Standard, None);
return;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
let filename_s = self.param.get(Param::File).unwrap_or_default();
filename = unsafe { filename_s.strdup() };
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_list = recipients
.split("\x1e")
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
Ok(addr) => Some(addr),
Err(err) => {
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
None
}
})
.collect::<Vec<_>>();
/* if there is a msg-id and it does not exist in the db, cancel sending.
this happends if dc_delete_msgs() was called
before the generated mime was sent out */
let ok_to_continue1;
if 0 != self.foreign_id {
if 0 == unsafe { dc_msg_exists(context, self.foreign_id) } {
warn!(
context,
0,
"Message {} for job {} does not exist",
self.foreign_id,
self.job_id,
);
ok_to_continue1 = false;
} else {
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = true;
}
if ok_to_continue1 {
/* send message */
let body = unsafe {
std::slice::from_raw_parts(buf as *const u8, buf_bytes).to_vec()
};
let filename = self.param.get(Param::File).unwrap_or_default();
let body = match dc_read_file_safe(context, filename) {
Some(bytes) => bytes,
None => {
warn!(context, 0, "job {} error", self.job_id);
return;
}
};
// hold the smtp lock during sending of a job and
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database as we
// otherwise might send it twice.
let mut sock = context.smtp.lock().unwrap();
if 0 == sock.send(context, recipients_list, body) {
sock.disconnect();
self.try_again_later(-1i32, Some(as_str(sock.error)));
} else {
dc_delete_file(context, filename_s);
if 0 != self.foreign_id {
dc_update_msg_state(
context,
self.foreign_id,
MessageState::OutDelivered,
);
let chat_id: i32 = context
.sql
.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
0,
)
.unwrap_or_default();
context.call_cb(
Event::MSG_DELIVERED,
chat_id as uintptr_t,
self.foreign_id as uintptr_t,
);
}
}
}
} else {
warn!(context, 0, "Missing recipients for job {}", self.job_id,);
let recipients = self.param.get(Param::Recipients);
if recipients.is_none() {
error!(context, 0, "Missing recipients for job {}", self.job_id,);
return;
}
let recipients_list = recipients
.unwrap()
.split("\x1e")
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
Ok(addr) => Some(addr),
Err(err) => {
eprintln!("WARNING: invalid recipient: {} {:?}", addr, err);
None
}
})
.collect::<Vec<_>>();
/* if there is a msg-id and it does not exist in the db, cancel sending.
this happends if dc_delete_msgs() was called
before the generated mime was sent out */
if 0 != self.foreign_id {
if !dc_msg_exists(context, self.foreign_id) {
warn!(
context,
0, "Message {} for job {} does not exist", self.foreign_id, self.job_id,
);
return;
}
}
unsafe { free(buf) };
unsafe { free(filename.cast()) };
/* send message while holding the smtp lock long enough
to also mark success in the database, to reduce chances
of a message getting sent twice.
*/
let mut sock = context.smtp.lock().unwrap();
if 0 == sock.send(context, recipients_list, body) {
sock.disconnect();
self.try_again_later(Delay::AtOnce, Some(as_str(sock.error)));
return;
}
dc_delete_file(context, filename);
if 0 != self.foreign_id {
dc_update_msg_state(context, self.foreign_id, MessageState::OutDelivered);
let chat_id: i32 = context
.sql
.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
0,
)
.unwrap_or_default();
context.call_cb(
Event::MSG_DELIVERED,
chat_id as uintptr_t,
self.foreign_id as uintptr_t,
);
}
}
// this value does not increase the number of tries
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<&str>) {
fn try_again_later(&mut self, try_again: Delay, pending_error: Option<&str>) {
self.try_again = try_again;
self.pending_error = pending_error.map(|s| s.to_string());
}
#[allow(non_snake_case)]
fn do_DC_JOB_MOVE_MSG(&mut self, context: &Context) {
let ok_to_continue;
let mut dest_uid = 0;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
if !connect_to_inbox(context, &inbox) {
self.try_again_later(Delay::Standard, None);
return;
}
if ok_to_continue {
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
let mut dest_uid = 0;
match inbox.mv(
context,
server_folder,
msg.server_uid,
&dest_folder,
&mut dest_uid,
) as libc::c_uint
{
1 => {
self.try_again_later(3i32, None);
}
3 => {
dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid);
}
0 | 2 | _ => {}
match inbox.mv(
context,
server_folder,
msg.server_uid,
&dest_folder,
&mut dest_uid,
) {
ImapResult::RetryLater => {
self.try_again_later(Delay::Standard, None);
}
ImapResult::Success => {
// TODO: dest_uid is not (yet) set by mv() so remains 0
dc_update_server_uid(
context,
as_str(msg.rfc724_mid),
&dest_folder,
dest_uid,
);
}
_ => {}
}
}
}
@@ -277,53 +243,34 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_DELETE_MSG_ON_IMAP(&mut self, context: &Context) {
let mut delete_from_server = 1;
let inbox = context.inbox.read().unwrap();
if let Ok(mut msg) = dc_msg_load_from_db(context, self.foreign_id) {
if !(msg.rfc724_mid.is_null()
|| unsafe { *msg.rfc724_mid.offset(0isize) as libc::c_int == 0 })
{
let ok_to_continue1;
/* eg. device messages have no Message-ID */
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
info!(
context,
0, "The message is deleted from the server when all parts are deleted.",
);
delete_from_server = 0i32
return;
}
/* if this is the last existing part of the message, we delete the message from the server */
if 0 != delete_from_server {
let ok_to_continue;
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
}
if ok_to_continue {
let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() };
let server_folder = msg.server_folder.as_ref().unwrap();
if 0 == inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) {
self.try_again_later(-1i32, None);
ok_to_continue1 = false;
} else {
ok_to_continue1 = true;
}
} else {
ok_to_continue1 = false;
}
} else {
ok_to_continue1 = true;
if !connect_to_inbox(context, &inbox) {
self.try_again_later(Delay::Standard, None);
return;
}
if ok_to_continue1 {
dc_delete_msg_from_db(context, msg.id);
let mid = unsafe { CStr::from_ptr(msg.rfc724_mid).to_str().unwrap() };
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(Delay::AtOnce, None);
}
_ => {
dc_delete_msg_from_db(context, msg.id);
}
}
}
}
@@ -331,57 +278,71 @@ impl Job {
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MSG_ON_IMAP(&mut self, context: &Context) {
let ok_to_continue;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3i32, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
if !connect_to_inbox(context, &inbox) {
self.try_again_later(Delay::Standard, None);
return;
}
if ok_to_continue {
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
let server_folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, server_folder, msg.server_uid) as libc::c_uint {
0 => {}
1 => {
self.try_again_later(3i32, None);
}
_ => {
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) as libc::c_uint
{
1 => {
self.try_again_later(3i32, None);
}
3 => {
send_mdn(context, msg.id);
}
0 | 2 | _ => {}
}
}
}
if let Ok(msg) = dc_msg_load_from_db(context, self.foreign_id) {
if msg.server_uid == 0 {
// the message has likely been moved
// try again later
self.try_again_later(Delay::Standard, None);
return;
}
let server_folder = msg.server_folder.as_ref().unwrap();
info!(context, 0, "job_markseen_msg db id={}", self.foreign_id);
match inbox.set_seen(context, server_folder, msg.server_uid) {
ImapResult::Failed => {
info!(context, 0, "job_markseen_msg failed");
return;
}
ImapResult::RetryLater => {
info!(context, 0, "job_markseen_msg retry-later");
self.try_again_later(Delay::Standard, None);
return;
}
_ => {}
};
info!(
context,
0,
"set_mdnsent wantsmdn={} mdns_enabled={}",
msg.param.get_int(Param::WantsMdn).unwrap_or_default(),
context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
);
/* XXX
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1)
*/
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(Delay::Standard, None);
}
ImapResult::Success => {
send_mdn(context, msg.id);
}
ImapResult::AlreadyDone => {}
ImapResult::Failed => {}
};
}
}
}
#[allow(non_snake_case)]
fn do_DC_JOB_MARKSEEN_MDN_ON_IMAP(&mut self, context: &Context) {
let ok_to_continue;
let folder = self
.param
.get(Param::ServerFolder)
@@ -391,37 +352,37 @@ impl Job {
let mut dest_uid = 0;
let inbox = context.inbox.read().unwrap();
if !inbox.is_connected() {
connect_to_inbox(context, &inbox);
if !inbox.is_connected() {
self.try_again_later(3, None);
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
if !connect_to_inbox(context, &inbox) {
self.try_again_later(Delay::Standard, None);
return;
}
if ok_to_continue {
if inbox.set_seen(context, &folder, uid) == 0 {
self.try_again_later(3i32, None);
match inbox.set_seen(context, &folder, uid) {
ImapResult::RetryLater => {
self.try_again_later(Delay::Standard, None);
return;
}
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
if 1 == inbox.mv(context, folder, uid, dest_folder, &mut dest_uid)
as libc::c_uint
{
self.try_again_later(3, None);
ImapResult::Failed => {
return;
}
_ => {}
};
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
inbox.configure_folders(context, 0x1i32);
}
let dest_folder = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
match inbox.mv(context, folder, uid, dest_folder, &mut dest_uid) {
ImapResult::RetryLater => {
self.try_again_later(Delay::Standard, None);
}
_ => {}
}
}
}
@@ -443,7 +404,7 @@ pub fn perform_imap_fetch(context: &Context) {
let inbox = context.inbox.read().unwrap();
let start = std::time::Instant::now();
if 0 == connect_to_inbox(context, &inbox) {
if !connect_to_inbox(context, &inbox) {
return;
}
if context
@@ -648,6 +609,21 @@ pub fn maybe_network(context: &Context) {
interrupt_sentbox_idle(context);
}
pub fn job_retry_msg_actions_at_once(context: &Context, msg_id: uint32_t) -> bool {
match sql::execute(
context,
&context.sql,
"UPDATE jobs SET desired_timestamp=? WHERE foreign_id=?;",
params![time(), msg_id],
) {
Ok(_) => true,
Err(_err) => {
info!(context, 0, "job-retry-at-once failed {:?}", _err);
false
}
}
}
pub fn job_action_exists(context: &Context, action: Action) -> bool {
context
.sql
@@ -725,7 +701,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 +713,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
@@ -815,7 +785,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
added_timestamp: row.get(4)?,
tries: row.get(6)?,
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
try_again: 0,
try_again: Delay::Default,
pending_error: None,
};
@@ -847,7 +817,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
// some configuration jobs are "exclusive":
// - they are always executed in the imap-thread and the smtp-thread is suspended during execution
// - they may change the database handle change the database handle; we do not keep old pointers therefore
// - they can be re-executed one time AT_ONCE, but they are not save in the database for later execution
// - they can be re-executed one time AtOnce, but they are not save in the database for later execution
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
job_kill_action(context, job.action);
&context
@@ -867,8 +837,8 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
let mut tries = 0;
while tries <= 1 {
// this can be modified by a job using dc_job_try_again_later()
job.try_again = 0;
// this can be modified by a job using try_again_later()
job.try_again = Delay::Default;
match job.action {
Action::SendMsgToSmtp => job.do_DC_JOB_SEND(context),
@@ -879,17 +849,17 @@ 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 => {}
}
if job.try_again != -1 {
if job.try_again != Delay::AtOnce {
break;
}
tries += 1
@@ -909,7 +879,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
.unsuspend(context);
suspend_smtp_thread(context, false);
break;
} else if job.try_again == 2 {
} else if job.try_again == Delay::IncreationPoll {
// just try over next loop unconditionally, the ui typically interrupts idle when the file (video) is ready
info!(
context,
@@ -922,7 +892,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
},
job.job_id
);
} else if job.try_again == -1 || job.try_again == 3 {
} else if job.try_again == Delay::AtOnce || job.try_again == Delay::Standard {
let tries = job.tries + 1;
if tries < 17 {
job.tries = tries;
@@ -998,15 +968,21 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
}
}
fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
let ret_connected = dc_connect_to_configured_imap(context, inbox);
if 0 != ret_connected {
inbox.set_watch_folder("INBOX".into());
fn connect_to_inbox(context: &Context, inbox: &Imap) -> bool {
if inbox.is_connected() {
true
} else {
let ret_connected = dc_connect_to_configured_imap(context, inbox);
if 0 != ret_connected {
inbox.set_watch_folder("INBOX".into());
true
} else {
false
}
}
ret_connected
}
fn send_mdn(context: &Context, msg_id: uint32_t) {
pub fn send_mdn(context: &Context, msg_id: uint32_t) {
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
if 0 != unsafe { dc_mimefactory_render(&mut mimefactory) } {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
@@ -1018,7 +994,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

@@ -94,6 +94,7 @@ pub enum LotState {
MsgInFresh = 10,
MsgInNoticed = 13,
MsgInSeen = 16,
MsgInMDNSent = 17,
MsgOutPreparing = 18,
MsgOutDraft = 19,
MsgOutPending = 20,

View File

@@ -30,6 +30,7 @@ pub enum MessageState {
InFresh = 10,
InNoticed = 13,
InSeen = 16,
InMDNSent = 17,
OutPreparing = 18,
OutDraft = 19,
OutPending = 20,
@@ -46,6 +47,7 @@ impl From<MessageState> for LotState {
InFresh => LotState::MsgInFresh,
InNoticed => LotState::MsgInNoticed,
InSeen => LotState::MsgInSeen,
InMDNSent => LotState::MsgInMDNSent,
OutPreparing => LotState::MsgOutPreparing,
OutDraft => LotState::MsgOutDraft,
OutPending => LotState::MsgOutPending,
@@ -80,7 +82,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 +95,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 +210,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 +385,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 +694,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 +826,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 +902,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 +927,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,
@@ -1030,39 +1035,32 @@ The value is also used for CC:-summaries */
// Context functions to work with messages
pub unsafe fn dc_msg_exists(context: &Context, msg_id: u32) -> libc::c_int {
if msg_id <= 9 {
return 0;
}
pub fn dc_msg_exists(context: &Context, msg_id: u32) -> bool {
if msg_id > DC_MSG_ID_LAST_SPECIAL {
let chat_id: Option<i32> = context.sql.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
let chat_id: Option<i32> = context.sql.query_row_col(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if let Some(chat_id) = chat_id {
if chat_id != 3 {
return 1;
if let Some(chat_id) = chat_id {
if chat_id != 3 {
return true;
}
}
}
0
false
}
pub fn dc_update_msg_move_state(
context: &Context,
rfc724_mid: *const libc::c_char,
state: MoveState,
) -> bool {
pub fn dc_update_msg_move_state(context: &Context, rfc724_mid: &str, state: MoveState) -> bool {
// we update the move_state for all messages belonging to a given Message-ID
// so that the state stay intact when parts are deleted
sql::execute(
context,
&context.sql,
"UPDATE msgs SET move_state=? WHERE rfc724_mid=?;",
params![state as i32, as_str(rfc724_mid)],
params![state as i32, rfc724_mid],
)
.is_ok()
}
@@ -1240,6 +1238,29 @@ pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: *const libc::c_char) ->
}
}
pub fn dc_rfc724_mid_exists_with_msg_state(
context: &Context,
rfc724_mid: &str,
) -> Option<(u32, MessageState, String, u32)> {
if rfc724_mid.is_empty() {
return None;
}
match context.sql.query_row(
"SELECT id, state, server_folder, server_uid FROM msgs WHERE rfc724_mid=?",
&[rfc724_mid],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
) {
Ok(res) => Some(res),
Err(_err) => {
info!(
context,
0, "dc_rfc724_mid_exists_with_msg_state ERR={:?}", _err
);
None
}
}
}
pub fn dc_rfc724_mid_exists(
context: &Context,
rfc724_mid: *const libc::c_char,
@@ -1265,7 +1286,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 };
@@ -1278,13 +1299,13 @@ pub fn dc_rfc724_mid_exists(
pub fn dc_update_server_uid(
context: &Context,
rfc724_mid: *const libc::c_char,
rfc724_mid: &str,
server_folder: impl AsRef<str>,
server_uid: u32,
) {
match context.sql.execute(
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;",
params![server_folder.as_ref(), server_uid, as_str(rfc724_mid)],
params![server_folder.as_ref(), server_uid, rfc724_mid],
) {
Ok(_) => {}
Err(err) => {

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

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

View File

@@ -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

@@ -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::*;
@@ -69,7 +70,7 @@ unsafe fn stress_functions(context: &Context) {
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: *mut libc::c_void = 0 as *mut libc::c_void;
let mut buf_bytes: size_t = 0;
assert_eq!(
@@ -162,10 +163,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 +175,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 +200,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 +227,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 +252,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 +265,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 +298,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 +354,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 +389,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 +417,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!(
@@ -652,6 +653,37 @@ unsafe fn create_test_context() -> TestContext {
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() };