Compare commits

..

20 Commits

Author SHA1 Message Date
holger krekel
4ef3a091fe parallelize tox-runs somewhat 2019-09-19 12:11:45 +02:00
holger krekel
977e3e08d7 refine python test run further 2019-09-19 11:37:26 +02:00
holger krekel
bb23e1487d try optimize python run a little 2019-09-19 11:24:21 +02:00
holger krekel
30783adef2 shuffle files such that ci_scripts/ contains all the ci scripts 2019-09-19 11:17:13 +02:00
holger krekel
a2c585c7a5 think these two env var settings are not needed 2019-09-19 10:51:09 +02:00
holger krekel
ff331061a0 remove unused script 2019-09-19 10:30:34 +02:00
holger krekel
77cb0276a6 try let upload of docs and python wheels outside docker and cache semantics 2019-09-19 09:46:47 +02:00
holger krekel
2747939b52 pyenv only exist in machine-runs -- let's see if upload passes without it or if there is a python path missing (the docker image contains several python versions) 2019-09-19 09:39:42 +02:00
dignifiedquire
dfb2ebb533 skip release and ffi runs, they are check using python bindings 2019-09-19 09:39:42 +02:00
dignifiedquire
4c579e6cf6 remove .git from cache dir, no need 2019-09-19 09:39:42 +02:00
dignifiedquire
cc1d520580 persist .git 2019-09-19 09:39:42 +02:00
dignifiedquire
c7686e0a97 fixup 2019-09-19 09:39:42 +02:00
dignifiedquire
d41bcccd41 add more details 2019-09-19 09:39:42 +02:00
dignifiedquire
3c1a4ebfe0 use correct script 2019-09-19 09:39:42 +02:00
dignifiedquire
361f14bffe ... 2019-09-19 09:39:42 +02:00
dignifiedquire
939ca7f7d3 .. 2019-09-19 09:39:42 +02:00
dignifiedquire
cdacf6a40f try fix config 2019-09-19 09:39:42 +02:00
dignifiedquire
feb4dfc3af try sth 2019-09-19 09:39:42 +02:00
dignifiedquire
8c13771d6c fix executor 2019-09-19 09:39:42 +02:00
dignifiedquire
5c3e1a6593 improve python caching 2019-09-19 09:39:42 +02:00
24 changed files with 457 additions and 498 deletions

7
Cargo.lock generated
View File

@@ -533,11 +533,12 @@ dependencies = [
[[package]]
name = "deltachat-provider-overview"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "git+https://github.com/deltachat/provider-overview?rev=366b41a7503973e4ffac3aa5173b419f2f03c211#366b41a7503973e4ffac3aa5173b419f2f03c211"
dependencies = [
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -554,7 +555,7 @@ name = "deltachat_ffi"
version = "1.0.0-alpha.4"
dependencies = [
"deltachat 1.0.0-alpha.4",
"deltachat-provider-overview 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deltachat-provider-overview 0.1.0 (git+https://github.com/deltachat/provider-overview?rev=366b41a7503973e4ffac3aa5173b419f2f03c211)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2974,7 +2975,7 @@ dependencies = [
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1"
"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b"
"checksum deltachat-provider-overview 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bef7b3626b0f859878db86ed54e4eef317adbcc3bcc3617eb38dec52e3f40e3"
"checksum deltachat-provider-overview 0.1.0 (git+https://github.com/deltachat/provider-overview?rev=366b41a7503973e4ffac3aa5173b419f2f03c211)" = "<none>"
"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4"
"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37"
"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839"

View File

@@ -39,12 +39,8 @@ if [ -n "$TESTS" ]; then
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- -k "qr"
tox --workdir "$TOXWORKDIR" -e py37
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels

View File

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

View File

@@ -11,16 +11,6 @@ extern "C" {
#endif
typedef struct _dc_context dc_context_t;
typedef struct _dc_array dc_array_t;
typedef struct _dc_chatlist dc_chatlist_t;
typedef struct _dc_chat dc_chat_t;
typedef struct _dc_msg dc_msg_t;
typedef struct _dc_contact dc_contact_t;
typedef struct _dc_lot dc_lot_t;
typedef struct _dc_provider dc_provider_t;
/**
* @mainpage Getting started
*
@@ -199,6 +189,13 @@ typedef struct _dc_provider dc_provider_t;
* SQLite database for offline functionality and for account-related
* settings.
*/
typedef struct _dc_context dc_context_t;
typedef struct _dc_array dc_array_t;
typedef struct _dc_chatlist dc_chatlist_t;
typedef struct _dc_chat dc_chat_t;
typedef struct _dc_msg dc_msg_t;
typedef struct _dc_contact dc_contact_t;
typedef struct _dc_lot dc_lot_t;
/**
@@ -445,6 +442,112 @@ char* dc_get_info (dc_context_t* context);
*/
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
/**
* Opaque object containing information about one single email provider.
*/
typedef struct _dc_provider dc_provider_t;
/**
* Create a provider struct for the given domain.
*
* @param doamin The domain to get provider info for.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_domain (char* domain);
/**
* Create a provider struct for the given email address.
*
* The provider is extracted from the email address and it's information is returned.
*
* @param email The user's email address to extract the provider info form.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_email (char* email);
/**
* URL of the overview page.
*
* This URL allows linking to the providers page on providers.delta.chat.
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_overview_page (const dc_provider_t* provider);
/**
* The provider's name.
*
* The name of the provider, e.g. "POSTEO".
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_name (const dc_provider_t* provider);
/**
* The markdown content of the providers page.
*
* This contains the preparation steps or additional information if the status
* is DC_PROVIDER_STATUS_BROKEN.
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_markdown (const dc_provider_t* provider);
/**
* Date of when the state was last checked/updated.
*
* This is returned as a string.
*
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_status_date (const dc_provider_t* provider);
/**
* Whether DC works with this provider.
*
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
*
* @param provider The dc_provider_t struct.
* @return The status as a constant number.
*/
int dc_provider_get_status (const dc_provider_t* provider);
/**
* Free the provider info struct.
*
* @param provider The dc_provider_t struct.
*/
void dc_provider_unref (const dc_provider_t* provider);
/**
* Provider status returned by dc_provider_get_status().
*
* Works right out of the box without any preperation steps needed
*/
#define DC_PROVIDER_STATUS_OK 1
/**
* Provider status returned by dc_provider_get_status().
*
* Works, but preparation steps are needed
*/
#define DC_PROVIDER_STATUS_PREPARATION 2
/**
* Provider status returned by dc_provider_get_status().
*
* Doesn't work (too unstable to use falls also in this category)
*/
#define DC_PROVIDER_STATUS_BROKEN 3
// connect
@@ -3421,110 +3524,6 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
int dc_contact_is_verified (dc_contact_t* contact);
/**
* @class dc_provider_t
*
* Opaque object containing information about one single email provider.
*/
/**
* Create a provider struct for the given domain.
*
* @memberof dc_provider_t
* @param domain The domain to get provider info for.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_domain (char* domain);
/**
* Create a provider struct for the given email address.
*
* The provider is extracted from the email address and it's information is returned.
*
* @memberof dc_provider_t
* @param email The user's email address to extract the provider info form.
* @return a dc_provider_t struct which can be used with the dc_provider_get_*
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_email (char* email);
/**
* URL of the overview page.
*
* This URL allows linking to the providers page on providers.delta.chat.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_overview_page (const dc_provider_t* provider);
/**
* The provider's name.
*
* The name of the provider, e.g. "POSTEO".
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_name (const dc_provider_t* provider);
/**
* The markdown content of the providers page.
*
* This contains the preparation steps or additional information if the status
* is @ref DC_PROVIDER_STATUS_BROKEN.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_markdown (const dc_provider_t* provider);
/**
* Date of when the state was last checked/updated.
*
* This is returned as a string.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return A string which must be free()d.
*/
char* dc_provider_get_status_date (const dc_provider_t* provider);
/**
* Whether DC works with this provider.
*
* Can be one of @ref DC_PROVIDER_STATUS_OK, @ref
* DC_PROVIDER_STATUS_PREPARATION and @ref DC_PROVIDER_STATUS_BROKEN.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
* @return The status as a constant number.
*/
int dc_provider_get_status (const dc_provider_t* provider);
/**
* Free the provider info struct.
*
* @memberof dc_provider_t
* @param provider The dc_provider_t struct.
*/
void dc_provider_unref (const dc_provider_t* provider);
/**
* @class dc_lot_t
*
@@ -4132,41 +4131,6 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_SHOW_EMAILS_ALL 2
/**
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
*
* These constants are used as return values for dc_provider_get_status().
*
* @addtogroup DC_PROVIDER_STATUS
* @{
*/
/**
* Provider status returned by dc_provider_get_status().
*
* Works right out of the box without any preperation steps needed
*/
#define DC_PROVIDER_STATUS_OK 1
/**
* Provider status returned by dc_provider_get_status().
*
* Works, but preparation steps are needed
*/
#define DC_PROVIDER_STATUS_PREPARATION 2
/**
* Provider status returned by dc_provider_get_status().
*
* Doesn't work (too unstable to use falls also in this category)
*/
#define DC_PROVIDER_STATUS_BROKEN 3
/**
* @}
*/
/*
* TODO: Strings need some doumentation about used placeholders.
*

View File

@@ -231,7 +231,7 @@ class Account(object):
:returns: a :class:`deltachat.chatting.Chat` object.
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name)
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
return Chat(self, chat_id)
def get_chats(self):
@@ -378,15 +378,6 @@ class Account(object):
raise ValueError("could not join group")
return Chat(self, chat_id)
#
# meta API for start/stop and event based processing
#
def wait_next_incoming_message(self):
""" wait for and return next incoming message. """
ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
return self.get_message_by_id(ev[2])
def start_threads(self):
""" start IMAP/SMTP threads (and configure account if it hasn't happened).

View File

@@ -109,13 +109,6 @@ class Chat(object):
"""
return not lib.dc_chat_is_unpromoted(self._dc_chat)
def is_verified(self):
""" return True if this chat is a verified group.
:returns: True if chat is verified, False otherwise.
"""
return lib.dc_chat_is_verified(self._dc_chat)
def get_name(self):
""" return name of this chat.

View File

@@ -101,10 +101,6 @@ class Message(object):
""" return True if this message is a setup message. """
return lib.dc_msg_is_setupmessage(self._dc_msg)
def is_encrypted(self):
""" return True if this message was encrypted. """
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
def get_message_info(self):
""" Return informational text for a single message.

View File

@@ -150,7 +150,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
lib.dc_set_config(ac._dc_context, b"configured", b"1")
return ac
def get_online_config(self):
def get_online_configuring_account(self):
if not session_liveconfig:
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
configdict = session_liveconfig.get(self.live_count)
@@ -161,10 +161,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
return ac, dict(configdict)
def get_online_configuring_account(self):
ac, configdict = self.get_online_config()
ac.configure(**configdict)
ac.start_threads()
return ac
@@ -210,13 +206,12 @@ def lp():
return Printer()
def wait_configuration_progress(account, min_target, max_target=1001):
min_target = min(min_target, max_target)
def wait_configuration_progress(account, target):
while 1:
evt_name, data1, data2 = \
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
if data1 >= min_target and data1 <= max_target:
print("** CONFIG PROGRESS {}".format(min_target), account)
if data1 >= target:
print("** CONFIG PROGRESS {}".format(target), account)
break

View File

@@ -443,7 +443,6 @@ class TestOnlineAccount:
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
assert not msg_out.is_encrypted()
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -461,7 +460,6 @@ class TestOnlineAccount:
assert ev[2] > msg_out.id
msg_back = ac1.get_message_by_id(ev[2])
assert msg_back.text == "message-back"
assert msg_back.is_encrypted()
def test_saved_mime_on_received_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -566,38 +564,6 @@ class TestOnlineAccount:
assert ch.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
def test_qr_verified_group_and_chatting(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
chat1 = ac1.create_group_chat("hello", verified=True)
assert chat1.is_verified()
qr = chat1.get_join_qr()
lp.sec("ac2: start QR-code based join-group protocol")
chat2 = ac2.qr_join_chat(qr)
assert chat2.id >= 10
wait_securejoin_inviter_progress(ac1, 1000)
lp.sec("ac2: read member added message")
msg = ac2.wait_next_incoming_message()
assert msg.is_encrypted()
assert "added" in msg.text.lower()
lp.sec("ac1: send message")
msg_out = chat1.send_text("hello")
assert msg_out.is_encrypted()
lp.sec("ac2: read message and check it's verified chat")
msg = ac2.wait_next_incoming_message()
assert msg.text == "hello"
assert msg.chat.is_verified()
assert msg.is_encrypted()
lp.sec("ac2: send message and let ac1 read it")
chat2.send_text("world")
msg = ac1.wait_next_incoming_message()
assert msg.text == "world"
assert msg.is_encrypted()
def test_set_get_profile_image(self, acfactory, data, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -650,32 +616,3 @@ class TestOnlineAccount:
chat1b = ac1.create_chat_by_message(ev[2])
assert chat1b.get_profile_image() is None
assert chat.get_profile_image() is None
class TestOnlineConfigureFails:
def test_invalid_password(self, acfactory):
ac1, configdict = acfactory.get_online_config()
ac1.configure(addr=configdict["addr"], mail_pw="123")
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_user(self, acfactory):
ac1, configdict = acfactory.get_online_config()
ac1.configure(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"])
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_domain(self, acfactory):
ac1, configdict = acfactory.get_online_config()
ac1.configure(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"])
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "could not connect" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)

View File

@@ -59,16 +59,6 @@ def test_wrong_db(tmpdir):
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
def test_empty_blobdir(tmpdir):
# Apparently some client code expects this to be the same as passing NULL.
ctx = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
db_fname = tmpdir.join("hello.db")
assert lib.dc_open(ctx, db_fname.strpath.encode("ascii"), b"")
def test_event_defines():
assert const.DC_EVENT_INFO == 100
assert const.DC_CONTACT_ID_SELF

View File

@@ -1,5 +1,6 @@
use std::ffi::CString;
use std::path::{Path, PathBuf};
use std::ptr;
use crate::chatlist::*;
use crate::config::*;
@@ -15,6 +16,7 @@ use crate::message::*;
use crate::param::*;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
use crate::x::*;
/// An object representing a single chat in memory.
/// Chat objects are created using eg. `Chat::load_from_db`
@@ -270,7 +272,7 @@ impl Chat {
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
_ => None,
};
dc_create_outgoing_rfc724_mid(grpid, &from)
dc_create_outgoing_rfc724_mid_safe(grpid, &from)
};
if self.typ == Chattype::Single {
@@ -1838,7 +1840,12 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
}
pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
let rfc724_mid = unsafe {
dc_create_outgoing_rfc724_mid(
ptr::null(),
b"@device\x00" as *const u8 as *const libc::c_char,
)
};
if context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);",
@@ -1850,13 +1857,21 @@ pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
Viewtype::Text,
MessageState::InNoticed,
text.as_ref(),
rfc724_mid,
as_str(rfc724_mid),
]
).is_err() {
unsafe { free(rfc724_mid as *mut libc::c_void) };
return;
}
let msg_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid);
let msg_id = sql::get_rowid(
context,
&context.sql,
"msgs",
"rfc724_mid",
as_str(rfc724_mid),
);
unsafe { free(rfc724_mid as *mut libc::c_void) };
context.call_cb(Event::MsgsChanged { chat_id, msg_id });
}

View File

@@ -18,7 +18,7 @@ use auto_mozilla::moz_autoconfigure;
macro_rules! progress {
($context:tt, $progress:expr) => {
assert!(
$progress <= 1000,
$progress > 0 && $progress <= 1000,
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
);
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
@@ -46,23 +46,17 @@ pub fn dc_is_configured(context: &Context) -> bool {
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
#[allow(non_snake_case, unused_must_use)]
pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
let mut success = false;
let mut imap_connected_here = false;
let mut smtp_connected_here = false;
let mut ongoing_allocated_here = false;
if !dc_alloc_ongoing(context) {
progress!(context, 0);
return;
}
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
progress!(context, 0);
return;
}
let mut success = false;
let mut imap_connected_here = false;
let mut smtp_connected_here = false;
let mut param_autoconfig: Option<LoginParam> = None;
let mut param_autoconfig: Option<LoginParam> = None;
if dc_alloc_ongoing(context) {
ongoing_allocated_here = true;
if !context.sql.is_open() {
error!(context, "Cannot configure, database not opened.",);
} else {
context.inbox.read().unwrap().disconnect(context);
context
.sentbox_thread
@@ -345,18 +339,26 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
progress!(context, 600);
/* try to connect to IMAP - if we did not got an autoconfig,
do some further tries with different settings and username variations */
for username_variation in 0..2 {
let ok_to_continue8;
let mut username_variation = 0;
loop {
if !(username_variation <= 1) {
ok_to_continue8 = true;
break;
}
info!(context, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
imap_connected_here = true;
ok_to_continue8 = true;
break;
}
if param_autoconfig.is_some() {
if !param_autoconfig.is_none() {
ok_to_continue8 = false;
break;
}
// probe STARTTLS/993
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 650 + username_variation * 30);
@@ -365,11 +367,12 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
info!(context, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
imap_connected_here = true;
ok_to_continue8 = true;
break;
}
// probe STARTTLS/143
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 660 + username_variation * 30);
@@ -377,14 +380,16 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
info!(context, "Trying: {}", &param);
if context.inbox.read().unwrap().connect(context, &param) {
imap_connected_here = true;
ok_to_continue8 = true;
break;
}
if username_variation > 0 {
if 0 != username_variation {
ok_to_continue8 = false;
break;
}
// next probe round with only the localpart of the email-address as the loginname
if s.shall_stop_ongoing {
ok_to_continue8 = false;
break;
}
progress!(context, 670 + username_variation * 30);
@@ -398,11 +403,18 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
if let Some(at) = param.send_user.find('@') {
param.send_user = param.send_user.split_at(at).0.to_string();
}
username_variation += 1
}
imap_connected_here
if ok_to_continue8 {
// success, so we are connected and should disconnect in cleanup
imap_connected_here = true;
}
ok_to_continue8
}
15 => {
progress!(context, 800);
let success;
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
if !context
.smtp
@@ -411,7 +423,11 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
.unwrap()
.connect(context, &param)
{
if param_autoconfig.is_none() && !s.shall_stop_ongoing {
if !param_autoconfig.is_none() {
success = false;
} else if s.shall_stop_ongoing {
success = false;
} else {
progress!(context, 850);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
@@ -425,31 +441,38 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
.unwrap()
.connect(context, &param)
{
if !s.shall_stop_ongoing {
if s.shall_stop_ongoing {
success = false;
} else {
progress!(context, 860);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 25;
info!(context, "Trying: {}", &param);
if context
if !context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
smtp_connected_here = true;
success = false;
} else {
success = true;
}
}
} else {
smtp_connected_here = true;
success = true;
}
}
} else {
success = true;
}
if success {
smtp_connected_here = true;
}
smtp_connected_here
success
}
16 => {
progress!(context, 900);
@@ -509,6 +532,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
break;
}
}
}
}
if imap_connected_here {
context.inbox.read().unwrap().disconnect(context);
}
@@ -533,7 +558,9 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
);
}
*/
dc_free_ongoing(context);
if ongoing_allocated_here {
dc_free_ongoing(context);
}
progress!(context, if success { 1000 } else { 0 });
}
@@ -633,36 +660,3 @@ pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
}
}
}
#[cfg(test)]
mod tests {
use crate::config::*;
use crate::configure::dc_job_do_DC_JOB_CONFIGURE_IMAP;
use crate::job::*;
use crate::param::*;
use crate::test_utils::*;
#[test]
fn test_no_panic_on_bad_credentials() {
let t = dummy_context();
t.ctx
.set_config(Config::Addr, Some("probably@unexistant.addr"))
.unwrap();
t.ctx.set_config(Config::MailPw, Some("123456")).unwrap();
let job = Job {
job_id: 1,
action: Action::ConfigureImap,
foreign_id: 0,
desired_timestamp: 0,
added_timestamp: 0,
tries: 0,
param: Params::new(),
try_again: 0,
pending_error: None,
};
unsafe {
dc_job_do_DC_JOB_CONFIGURE_IMAP(&t.ctx, &job);
}
}
}

View File

@@ -362,7 +362,7 @@ impl Context {
}
if self.is_mvbox(folder) {
dc_update_msg_move_state(self, &msg.rfc724_mid, MoveState::Stay);
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Stay);
}
// 1 = dc message, 2 = reply to dc message
@@ -374,7 +374,7 @@ impl Context {
Params::new(),
0,
);
dc_update_msg_move_state(self, &msg.rfc724_mid, MoveState::Moving);
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Moving);
}
}
}

View File

@@ -961,23 +961,22 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
let private_key_blob: Vec<u8> = row.get(2)?;
let private_key = Key::from_slice(&private_key_blob, KeyType::Private);
let is_default: i32 = row.get(3)?;
let is_default = row.get(3)?;
Ok((id, public_key, private_key, is_default))
},
|keys| {
for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?;
let id = Some(id).filter(|_| is_default != 0);
if let Some(key) = public_key {
if export_key_to_asc_file(context, dir, id, &key) {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
export_errors += 1;
}
if let Some(key) = private_key {
if export_key_to_asc_file(context, dir, id, &key) {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
@@ -999,15 +998,26 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
unsafe fn export_key_to_asc_file(
context: &Context,
dir: *const libc::c_char,
id: Option<i64>,
id: libc::c_int,
key: &Key,
is_default: libc::c_int,
) -> bool {
let mut success = false;
let file_name = {
let kind = if key.is_public() { "public" } else { "private" };
let id = id.map_or("default".into(), |i| i.to_string());
let dir = as_path(dir);
as_path(dir).join(format!("{}-key-{}.asc", kind, &id))
let file_name = if 0 != is_default {
let name = format!(
"{}-key-default.asc",
if key.is_public() { "public" } else { "private" },
);
dir.join(name)
} else {
let name = format!(
"{}-key-{}.asc",
if key.is_public() { "public" } else { "private" },
id
);
dir.join(name)
};
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name);
@@ -1015,7 +1025,7 @@ unsafe fn export_key_to_asc_file(
if !key.write_asc_to_file(&file_name, context) {
error!(context, "Cannot write key to {}", file_name.display());
} else {
context.call_cb(Event::ImexFileWritten(file_name));
context.call_cb(Event::ImexFileWritten(file_name.clone()));
success = true;
}
@@ -1081,25 +1091,4 @@ mod tests {
assert_eq!(setupcode.chars().nth(34).unwrap(), '-');
assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
}
#[test]
fn test_export_key_to_asc_file() {
unsafe {
let context = dummy_context();
let base64 = include_str!("../test-data/key/public.asc");
let key = Key::from_base64(base64, KeyType::Public).unwrap();
let blobdir = CString::yolo("$BLOBDIR");
assert!(export_key_to_asc_file(
&context.ctx,
blobdir.as_ptr(),
None,
&key
));
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
let filename = format!("{}/public-key-default.asc", blobdir);
let bytes = std::fs::read(&filename).unwrap();
assert_eq!(bytes, key.to_asc(None).into_bytes());
}
}
}

View File

@@ -36,7 +36,7 @@ pub struct dc_mimefactory_t<'a> {
pub recipients_names: *mut clist,
pub recipients_addr: *mut clist,
pub timestamp: i64,
pub rfc724_mid: String,
pub rfc724_mid: *mut libc::c_char,
pub loaded: dc_mimefactory_loaded_t,
pub msg: Message,
pub chat: Option<Chat>,
@@ -57,6 +57,7 @@ impl<'a> Drop for dc_mimefactory_t<'a> {
unsafe {
free(self.from_addr as *mut libc::c_void);
free(self.from_displayname as *mut libc::c_void);
free(self.rfc724_mid as *mut libc::c_void);
if !self.recipients_names.is_null() {
clist_free_content(self.recipients_names);
clist_free(self.recipients_names);
@@ -97,7 +98,7 @@ pub unsafe fn dc_mimefactory_load_msg(
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
rfc724_mid: String::default(),
rfc724_mid: ptr::null_mut(),
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: Some(chat),
@@ -231,7 +232,7 @@ pub unsafe fn dc_mimefactory_load_msg(
factory.loaded = DC_MF_MSG_LOADED;
factory.timestamp = factory.msg.timestamp_sort;
factory.rfc724_mid = factory.msg.rfc724_mid.clone();
factory.rfc724_mid = dc_strdup(factory.msg.rfc724_mid);
factory.increation = dc_msg_is_increation(&factory.msg);
Ok(factory)
@@ -287,7 +288,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
rfc724_mid: String::default(),
rfc724_mid: ptr::null_mut(),
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: None,
@@ -329,7 +330,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
);
load_from(&mut factory);
factory.timestamp = dc_create_smeared_timestamp(factory.context);
factory.rfc724_mid = dc_create_outgoing_rfc724_mid(None, as_str(factory.from_addr));
factory.rfc724_mid = dc_create_outgoing_rfc724_mid(0 as *const libc::c_char, factory.from_addr);
factory.loaded = DC_MF_MDN_LOADED;
Ok(factory)
@@ -446,7 +447,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
to,
ptr::null_mut(),
ptr::null_mut(),
factory.rfc724_mid.strdup(),
dc_strdup(factory.rfc724_mid),
in_reply_to_list,
references_list,
ptr::null_mut(),
@@ -950,7 +951,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
version,
as_str(factory.from_addr),
as_str(factory.from_addr),
factory.msg.rfc724_mid
as_str(factory.msg.rfc724_mid)
).strdup();
let content_type_0: *mut mailmime_content = mailmime_content_new_with_str(

View File

@@ -995,19 +995,6 @@ impl<'a> MimeParser<'a> {
assert_eq!(self.parts.len(), 1);
}
pub fn get_rfc724_mid(&mut self) -> Option<String> {
// get Message-ID from header
if let Some(field) = self.lookup_field_typ("Message-ID", MAILIMF_FIELD_MESSAGE_ID) {
unsafe {
let fld_message_id = (*field).fld_data.fld_message_id;
if !fld_message_id.is_null() {
return Some(to_string((*fld_message_id).mid_value));
}
}
}
None
}
}
impl<'a> Drop for MimeParser<'a> {
@@ -1654,27 +1641,6 @@ mod tests {
}
}
#[test]
fn test_get_rfc724_mid_exists() {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/mail_with_message_id.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]) };
assert_eq!(
mimeparser.get_rfc724_mid(),
Some("2dfdbde7@example.org".into())
);
}
#[test]
fn test_get_rfc724_mid_not_exists() {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]) };
assert_eq!(mimeparser.get_rfc724_mid(), None);
}
#[test]
fn test_mimeparser_with_context() {
unsafe {

View File

@@ -85,6 +85,8 @@ pub unsafe fn dc_receive_imf(
let mut add_delete_job: libc::c_int = 0;
let mut insert_msg_id = 0;
// Message-ID from the header
let rfc724_mid = std::ptr::null_mut();
let mut sent_timestamp = 0;
let mut created_db_entries = Vec::new();
let mut create_event_to_send = Some(CreateEvent::MsgsChanged);
@@ -94,9 +96,12 @@ pub unsafe fn dc_receive_imf(
// helper method to handle early exit and memory cleanup
let cleanup = |context: &Context,
rfc724_mid: *mut libc::c_char,
create_event_to_send: &Option<CreateEvent>,
created_db_entries: &Vec<(usize, usize)>,
rr_event_to_send: &Vec<(u32, u32)>| {
free(rfc724_mid.cast());
if let Some(create_event_to_send) = create_event_to_send {
for (chat_id, msg_id) in created_db_entries {
let event = match create_event_to_send {
@@ -179,28 +184,6 @@ pub unsafe fn dc_receive_imf(
}
// Add parts
let rfc724_mid = match mime_parser.get_rfc724_mid() {
Some(x) => x,
None => {
// missing Message-IDs may come if the mail was set from this account with another
// client that relies in the SMTP server to generate one.
// true eg. for the Webmailer used in all-inkl-KAS
match dc_create_incoming_rfc724_mid(sent_timestamp, from_id, &to_ids) {
Some(x) => x.to_string(),
None => {
error!(context, "can not create incoming rfc724_mid");
cleanup(
context,
&create_event_to_send,
&created_db_entries,
&rr_event_to_send,
);
return;
}
}
}
};
if mime_parser.get_last_nonmeta().is_some() {
if let Err(err) = add_parts(
context,
@@ -212,7 +195,7 @@ pub unsafe fn dc_receive_imf(
server_folder.as_ref(),
server_uid,
&mut to_ids,
&rfc724_mid,
rfc724_mid,
&mut sent_timestamp,
&mut from_id,
from_id_blocked,
@@ -230,6 +213,7 @@ pub unsafe fn dc_receive_imf(
cleanup(
context,
rfc724_mid,
&create_event_to_send,
&created_db_entries,
&rr_event_to_send,
@@ -279,11 +263,14 @@ pub unsafe fn dc_receive_imf(
info!(
context,
"received message {} has Message-Id: {}", server_uid, rfc724_mid
"received message {} has Message-Id: {}",
server_uid,
to_string(rfc724_mid)
);
cleanup(
context,
rfc724_mid,
&create_event_to_send,
&created_db_entries,
&rr_event_to_send,
@@ -300,7 +287,7 @@ unsafe fn add_parts(
server_folder: impl AsRef<str>,
server_uid: u32,
to_ids: &mut Vec<u32>,
rfc724_mid: &str,
mut rfc724_mid: *mut libc::c_char,
sent_timestamp: &mut i64,
from_id: &mut u32,
from_id_blocked: i32,
@@ -349,6 +336,25 @@ unsafe fn add_parts(
}
}
// get Message-ID; if the header is lacking one, generate one based on fields that do never
// change. (missing Message-IDs may come if the mail was set from this account with another
// client that relies in the SMTP server to generate one.
// true eg. for the Webmailer used in all-inkl-KAS)
if let Some(field) = mime_parser.lookup_field_typ("Message-ID", MAILIMF_FIELD_MESSAGE_ID) {
let fld_message_id = (*field).fld_data.fld_message_id;
if !fld_message_id.is_null() {
rfc724_mid = dc_strdup((*fld_message_id).mid_value)
}
}
if rfc724_mid.is_null() {
rfc724_mid = dc_create_incoming_rfc724_mid(*sent_timestamp, *from_id, to_ids);
if rfc724_mid.is_null() {
cleanup(mime_in_reply_to, mime_references);
bail!("Cannot create Message-ID");
}
}
// 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) */
@@ -357,12 +363,12 @@ unsafe fn add_parts(
if 0 != dc_rfc724_mid_exists(
context,
&rfc724_mid,
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);
dc_update_server_uid(context, rfc724_mid, server_folder.as_ref(), server_uid);
}
free(old_server_folder.cast());
@@ -661,7 +667,7 @@ unsafe fn add_parts(
}
stmt.execute(params![
rfc724_mid,
as_str(rfc724_mid),
server_folder.as_ref(),
server_uid as libc::c_int,
*chat_id as libc::c_int,
@@ -692,8 +698,13 @@ unsafe fn add_parts(
])?;
txt_raw = None;
*insert_msg_id =
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
*insert_msg_id = sql::get_rowid_with_conn(
context,
conn,
"msgs",
"rfc724_mid",
as_str(rfc724_mid),
);
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
}
Ok(())
@@ -856,7 +867,7 @@ unsafe fn handle_reports(
if 0 != dc_mdn_from_ext(
context,
from_id,
as_str(rfc724_mid_0),
rfc724_mid_0,
sent_timestamp,
&mut chat_id_0,
&mut msg_id,
@@ -1859,11 +1870,11 @@ unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clis
while !cur.is_null() {
if 0 != is_msgrmsg_rfc724_mid(
context,
if !cur.is_null() {
as_str((*cur).data as *const libc::c_char)
(if !cur.is_null() {
(*cur).data
} else {
""
},
ptr::null_mut()
}) as *const libc::c_char,
) {
return 1;
}
@@ -1878,15 +1889,15 @@ unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clis
}
/// Check if a message is a reply to any messenger message.
fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> libc::c_int {
if rfc724_mid.is_empty() {
fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: *const libc::c_char) -> libc::c_int {
if rfc724_mid.is_null() {
return 0;
}
context
.sql
.exists(
"SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;",
params![rfc724_mid],
params![as_str(rfc724_mid)],
)
.unwrap_or_default() as libc::c_int
}

View File

@@ -572,9 +572,9 @@ pub fn dc_create_incoming_rfc724_mid(
message_timestamp: i64,
contact_id_from: u32,
contact_ids_to: &Vec<u32>,
) -> Option<String> {
) -> *mut libc::c_char {
if contact_ids_to.is_empty() {
return None;
return ptr::null_mut();
}
/* find out the largest receiver ID (we could also take the smallest, but it should be unique) */
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
@@ -583,14 +583,37 @@ pub fn dc_create_incoming_rfc724_mid(
"{}-{}-{}@stub",
message_timestamp, contact_id_from, largest_id_to
);
Some(result)
unsafe { result.strdup() }
}
/// Function generates a Message-ID that can be used for a new outgoing message.
/// - this function is called for all outgoing messages.
/// - the message ID should be globally unique
/// - do not add a counter or any private data as this leaks information unncessarily
pub fn dc_create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str) -> String {
/// - do not add a counter or any private data as as this may give unneeded information to the receiver
pub unsafe fn dc_create_outgoing_rfc724_mid(
grpid: *const libc::c_char,
from_addr: *const libc::c_char,
) -> *mut libc::c_char {
let rand2 = dc_create_id();
let at_hostname = as_opt_str(strchr(from_addr, '@' as i32)).unwrap_or_else(|| "@nohost");
let ret = if !grpid.is_null() {
format!("Gr.{}.{}{}", as_str(grpid), rand2, at_hostname,)
} else {
let rand1 = dc_create_id();
format!("Mr.{}.{}{}", rand1, rand2, at_hostname)
};
ret.strdup()
}
/// Generate globally-unique message-id for a new outgoing message.
///
/// Note: Do not add a counter or any private data as as this may give
/// unneeded information to the receiver
pub fn dc_create_outgoing_rfc724_mid_safe(grpid: Option<&str>, from_addr: &str) -> String {
let hostname = from_addr
.find('@')
.map(|k| &from_addr[k..])
@@ -1790,6 +1813,10 @@ mod tests {
#[test]
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
assert_eq!(res, Some("123-45-7@stub".into()));
assert_eq!(as_str(res), "123-45-7@stub");
unsafe {
free(res.cast());
}
}
}

View File

@@ -1,3 +1,4 @@
use std::ffi::CString;
use std::net;
use std::ptr;
use std::sync::{
@@ -9,6 +10,7 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_receive_imf::dc_receive_imf;
use crate::dc_tools::CStringExt;
use crate::dc_tools::*;
use crate::events::Event;
use crate::job::{job_add, Action};
@@ -819,7 +821,10 @@ impl Imap {
.message_id
.expect("missing message id");
if 0 == unsafe { precheck_imf(context, &message_id, folder.as_ref(), cur_uid) } {
if 0 == unsafe {
let message_id_c = CString::yolo(message_id);
precheck_imf(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
} {
// check passed, go fetch the rest
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
info!(
@@ -1640,7 +1645,7 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
unsafe fn precheck_imf(
context: &Context,
rfc724_mid: &str,
rfc724_mid: *const libc::c_char,
server_folder: &str,
server_uid: u32,
) -> libc::c_int {
@@ -1651,7 +1656,7 @@ unsafe fn precheck_imf(
let mut mark_seen: libc::c_int = 0i32;
msg_id = dc_rfc724_mid_exists(
context,
&rfc724_mid,
rfc724_mid,
&mut old_server_folder,
&mut old_server_uid,
);
@@ -1660,14 +1665,18 @@ unsafe fn precheck_imf(
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
&& old_server_uid == 0i32 as libc::c_uint
{
info!(context, "[move] detected bbc-self {}", rfc724_mid,);
info!(context, "[move] detected bbc-self {}", as_str(rfc724_mid),);
mark_seen = 1i32
} else if as_str(old_server_folder) != server_folder {
info!(context, "[move] detected moved message {}", rfc724_mid,);
dc_update_msg_move_state(context, &rfc724_mid, MoveState::Stay);
info!(
context,
"[move] detected moved message {}",
as_str(rfc724_mid),
);
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
dc_update_server_uid(context, &rfc724_mid, server_folder, server_uid);
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
context.do_heuristics_moves(server_folder, msg_id);
if 0 != mark_seen {

View File

@@ -1,3 +1,4 @@
use std::ffi::CStr;
use std::ptr;
use std::time::Duration;
@@ -252,7 +253,7 @@ impl Job {
self.try_again_later(3i32, None);
}
3 => {
dc_update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid);
dc_update_server_uid(context, msg.rfc724_mid, &dest_folder, dest_uid);
}
0 | 2 | _ => {}
}
@@ -267,10 +268,12 @@ impl Job {
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_empty() {
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 {
if dc_rfc724_mid_cnt(context, msg.rfc724_mid) != 1 {
info!(
context,
"The message is deleted from the server when all parts are deleted.",
@@ -292,10 +295,9 @@ impl Job {
ok_to_continue = true;
}
if ok_to_continue {
let mid = msg.rfc724_mid;
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)
{
if 0 == inbox.delete_msg(context, mid, server_folder, &mut msg.server_uid) {
self.try_again_later(-1i32, None);
ok_to_continue1 = false;
} else {
@@ -999,7 +1001,8 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
let mut success: libc::c_int = 0i32;
let mut recipients: *mut libc::c_char = ptr::null_mut();
let mut param = Params::new();
let path_filename = dc_get_fine_path_filename(context, "$BLOBDIR", &mimefactory.rfc724_mid);
let path_filename =
dc_get_fine_path_filename(context, "$BLOBDIR", as_str(mimefactory.rfc724_mid));
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
@@ -1010,7 +1013,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
error!(
context,
"Could not write message <{}> to \"{}\".",
mimefactory.rfc724_mid,
to_string(mimefactory.rfc724_mid),
path_filename.display(),
);
} else {

View File

@@ -151,7 +151,7 @@ pub struct Message {
pub timestamp_sent: i64,
pub timestamp_rcvd: i64,
pub text: Option<String>,
pub rfc724_mid: String,
pub rfc724_mid: *mut libc::c_char,
pub in_reply_to: Option<String>,
pub server_folder: Option<String>,
pub server_uid: u32,
@@ -292,8 +292,8 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
if !rawtxt.is_empty() {
ret += &format!("\n{}\n", rawtxt);
}
if !msg.rfc724_mid.is_empty() {
ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
if !msg.rfc724_mid.is_null() && 0 != *msg.rfc724_mid.offset(0) as libc::c_int {
ret += &format!("\nMessage-ID: {}", as_str(msg.rfc724_mid));
}
if let Some(ref server_folder) = msg.server_folder {
if server_folder != "" {
@@ -322,7 +322,7 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
timestamp_sent: 0,
timestamp_rcvd: 0,
text: None,
rfc724_mid: String::default(),
rfc724_mid: std::ptr::null_mut(),
in_reply_to: None,
server_folder: None,
server_uid: 0,
@@ -334,6 +334,14 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
}
}
impl Drop for Message {
fn drop(&mut self) {
unsafe {
free(self.rfc724_mid.cast());
}
}
}
pub fn dc_msg_get_filemime(msg: &Message) -> String {
if let Some(m) = msg.param.get(Param::MimeType) {
return m.to_string();
@@ -430,7 +438,7 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
unsafe {
let mut msg = dc_msg_new_untyped();
msg.id = row.get::<_, i32>(0)? as u32;
msg.rfc724_mid = row.get::<_, String>(1)?;
msg.rfc724_mid = row.get::<_, String>(1)?.strdup();
msg.in_reply_to = row.get::<_, Option<String>>(2)?;
msg.server_folder = row.get::<_, Option<String>>(3)?;
msg.server_uid = row.get(4)?;
@@ -988,14 +996,18 @@ pub fn dc_msg_exists(context: &Context, msg_id: u32) -> bool {
}
}
pub fn dc_update_msg_move_state(context: &Context, rfc724_mid: &str, state: MoveState) -> bool {
pub fn dc_update_msg_move_state(
context: &Context,
rfc724_mid: *const libc::c_char,
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, rfc724_mid],
params![state as i32, as_str(rfc724_mid)],
)
.is_ok()
}
@@ -1030,13 +1042,13 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
pub unsafe fn dc_mdn_from_ext(
context: &Context,
from_id: u32,
rfc724_mid: &str,
rfc724_mid: *const libc::c_char,
timestamp_sent: i64,
ret_chat_id: *mut u32,
ret_msg_id: *mut u32,
) -> libc::c_int {
if from_id <= 9
|| rfc724_mid.is_empty()
|| rfc724_mid.is_null()
|| ret_chat_id.is_null()
|| ret_msg_id.is_null()
|| *ret_chat_id != 0
@@ -1052,7 +1064,7 @@ pub unsafe fn dc_mdn_from_ext(
LEFT JOIN chats c ON m.chat_id=c.id \
WHERE rfc724_mid=? AND from_id=1 \
ORDER BY m.id;",
params![rfc724_mid],
params![as_str(rfc724_mid)],
|row| {
Ok((
row.get::<_, i32>(0)?,
@@ -1156,11 +1168,11 @@ pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
}
}
pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> libc::c_int {
pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: *const libc::c_char) -> libc::c_int {
/* check the number of messages with the same rfc724_mid */
match context.sql.query_row(
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
&[rfc724_mid],
&[as_str(rfc724_mid)],
|row| row.get(0),
) {
Ok(res) => res,
@@ -1173,16 +1185,16 @@ pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> libc::c_int {
pub fn dc_rfc724_mid_exists(
context: &Context,
rfc724_mid: &str,
rfc724_mid: *const libc::c_char,
ret_server_folder: *mut *mut libc::c_char,
ret_server_uid: *mut u32,
) -> u32 {
if rfc724_mid.is_empty() {
if rfc724_mid.is_null() || unsafe { *rfc724_mid.offset(0) as libc::c_int } == 0 {
return 0;
}
match context.sql.query_row(
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
&[rfc724_mid],
&[as_str(rfc724_mid)],
|row| {
if !ret_server_folder.is_null() {
unsafe { *ret_server_folder = row.get::<_, String>(0)?.strdup() };
@@ -1209,13 +1221,13 @@ pub fn dc_rfc724_mid_exists(
pub fn dc_update_server_uid(
context: &Context,
rfc724_mid: &str,
rfc724_mid: *const libc::c_char,
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, rfc724_mid],
params![server_folder.as_ref(), server_uid, as_str(rfc724_mid)],
) {
Ok(_) => {}
Err(err) => {

View File

@@ -68,11 +68,94 @@ pub fn configure_alice_keypair(ctx: &Context) -> String {
// .unwrap();
// println!("{}", public.to_base64(64));
// println!("{}", private.to_base64(64));
let public =
key::Key::from_base64(include_str!("../test-data/key/public.asc"), KeyType::Public)
.unwrap();
let public = key::Key::from_base64(
concat!(
"xsBNBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1l",
"FrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSX",
"AMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4",
"Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9",
"iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchw",
"oFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAHNEzxhbGljZUBleGFtcGxl",
"LmNvbT7CwIkEEAEIADMCGQEFAl086fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iai",
"x4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9",
"OHUl3MrXtZ7QmHyOAFvbXE/6n5Eeh+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkK",
"A8e4cJqwDOHsyAnvQXZ7WNje9+BMzcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea",
"6zjGF0/qljTdoxTtsYpv5wXYuhwbYklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6",
"GkquJN814Y+xny4xhZzGOfue6SeP12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUK",
"u5wO9FFbgDySOSlEjByGejSGuBmho0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxD",
"Fc7ATQRdPOnsAQgA5oLxXRLnyugzOmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG",
"9JzDeQql+sYXgUSxOoIayItuXtnFn7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av",
"62n18Venlm0yNKpROPcZ6M/sc4m6uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/R",
"noW+/fhmwIg08dQ5m8hQe3GEOZEeLrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q",
"4zW8vk2ztB8ngwbnqYy8zrN1DCICN1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAm",
"jxLZfVDcoObFH3Cv2GB7BEYxv86KC2Y6T74Q/wARAQABwsB2BBgBCAAgBQJdPOn4",
"AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/dXshJnoW",
"qEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJywJoupwX",
"FNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJuiCQvR9m",
"MjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6RDXIeYJf",
"qrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMlammDliPw",
"sK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKObzPqgJCGw",
"jTglkixw+aSTXw=="
),
KeyType::Public,
)
.unwrap();
let private = key::Key::from_base64(
include_str!("../test-data/key/private.asc"),
concat!(
"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",
"zPqgJCGwjTglkixw+aSTXw=="
),
KeyType::Private,
)
.unwrap();

View File

@@ -1 +0,0 @@
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

View File

@@ -1,13 +0,0 @@
Return-Path: <x@testrun.org>
Received: from hq5.merlinux.eu
by hq5.merlinux.eu (Dovecot) with LMTP id yRKOBakcfV1AewAAPzvFDg
; Sat, 14 Sep 2019 19:00:25 +0200
Received: from localhost (unknown 7.165.105.24])
by hq5.merlinux.eu (Postfix) with ESMTPSA id 8D9844E023;
Sat, 14 Sep 2019 19:00:22 +0200 (CEST)
message-id: <2dfdbde7@example.org>
Date: Sat, 14 Sep 2019 19:00:13 +0200
From: lmn <x@tux.org>
To: abc <abc@bcd.com>, def <def@def.de>,
jik