Compare commits

..

2 Commits

Author SHA1 Message Date
Alexander Krotov
a1a34bc585 Get rid of unnecessary "async move" and ".await" 2019-12-16 19:19:01 +03:00
holger krekel
5814f2d629 try doing an smtp-send timeout 2019-12-16 17:09:37 +01:00
26 changed files with 410 additions and 235 deletions

View File

@@ -1,19 +1,5 @@
# Changelog
## 1.0.0-beta.20
- #1074 fix OAUTH2/gmail
- #1072 fix group members not appearing in contact list
- #1071 never block interrupt_idle (thus hopefully also not on maybe_network())
- #1069 reduce smtp-timeout to 30 seconds
- #1066 #1065 avoid unwrap in dehtml, make literals more readable
## 1.0.0-beta.19
- #1058 timeout smtp-send if it doesn't complete in 15 minutes
- #1059 trim down logging
## 1.0.0-beta.18
- #1056 avoid panicking when we couldn't read imap-server's greeting

19
Cargo.lock generated
View File

@@ -85,7 +85,7 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.1.1"
source = "git+https://github.com/async-email/async-imap?branch=dcc-stable#50e843113e3a67e924a8a14c477833da3ebc1b44"
source = "git+https://github.com/async-email/async-imap?branch=dcc-stable#4f0852971c3f397c2b8a343ef1378992c02f61de"
dependencies = [
"async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -96,7 +96,6 @@ dependencies = [
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures_codec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"imap-proto 0.9.1 (git+https://github.com/djc/tokio-imap)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -125,7 +124,7 @@ dependencies = [
[[package]]
name = "async-smtp"
version = "0.1.0"
source = "git+https://github.com/async-email/async-smtp#6a4830032953f06020edc09db8daa06193e2b93f"
source = "git+https://github.com/async-email/async-smtp#c26ce542e847c502654c471ebc50d6c996f86cee"
dependencies = [
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -658,7 +657,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.20"
version = "1.0.0-beta.18"
dependencies = [
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)",
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -716,13 +715,12 @@ dependencies = [
[[package]]
name = "deltachat-provider-database"
version = "0.3.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.103 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -736,15 +734,14 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.20"
version = "1.0.0-beta.18"
dependencies = [
"deltachat 1.0.0-beta.20",
"deltachat-provider-database 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deltachat 1.0.0-beta.18",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -3535,7 +3532,7 @@ dependencies = [
"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b"
"checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
"checksum deltachat-provider-database 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc18fb898a7bc90565c801d7bd17a4b61478f0da0ea06ecf7f0428bd20242c52"
"checksum deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "814dba060d9fdc7a989fccdc4810ada9d1c7a1f09131c78e42412bc6c634b93b"
"checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
"checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
"checksum des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-beta.20"
version = "1.0.0-beta.18"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
@@ -17,7 +17,7 @@ smallvec = "1.0.0"
reqwest = { version = "0.9.15" }
num-derive = "0.3.0"
num-traits = "0.2.6"
async-smtp = { git = "https://github.com/async-email/async-smtp" }
async-smtp = { git = "https://github.com/async-email/async-smtp", branch = "master" }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "native_tls" }

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.20"
version = "1.0.0-beta.18"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
@@ -16,12 +16,11 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
deltachat = { path = "../", default-features = false }
deltachat-provider-database = "0.3.0"
deltachat-provider-database = "0.2.1"
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
failure = "0.1.6"
serde_json = "1.0"
[features]
default = ["vendored", "nightly", "ringbuf"]

View File

@@ -18,6 +18,7 @@ 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;
/**
@@ -3659,30 +3660,106 @@ int dc_contact_is_verified (dc_contact_t* contact);
/**
* @class dc_provider_t
*
* Opaque object containing information about one single email provider.
*/
/**
* Get the provider json object for the given domain.
* For more documentation see https://docs.rs/deltachat-provider-database/
* Create a provider struct for the given domain.
*
* @memberof dc_provider_t
* @param domain The domain to get provider info for.
* @return a provider json object as string. If no provider info is found, an empty string will be returned.
* @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.
*/
char* dc_provider_json_from_domain (const char* domain);
dc_provider_t* dc_provider_new_from_domain (const char* domain);
/**
* Get the provider json object for the given email address.
* For more documentation see https://docs.rs/deltachat-provider-database/
* 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 provider json object as string. If no provider info is found, an empty string will be returned.
* @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.
*/
char* dc_provider_json_from_email (const char* email);
dc_provider_t* dc_provider_new_from_email (const 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 released using dc_str_unref().
*/
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 released using dc_str_unref().
*/
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 released using dc_str_unref().
*/
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 released using dc_str_unref().
*/
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);
/**
@@ -4398,6 +4475,41 @@ 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

@@ -509,7 +509,7 @@ pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) {
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| job::interrupt_inbox_idle(ctx))
.with_inner(|ctx| job::interrupt_inbox_idle(ctx, true))
.unwrap_or(())
}

View File

@@ -1,35 +1,93 @@
extern crate deltachat_provider_database;
use std::ptr;
use crate::string::{to_string_lossy, StrExt};
use deltachat_provider_database::StatusState;
#[no_mangle]
pub type dc_provider_t = deltachat_provider_database::Provider;
#[no_mangle]
pub unsafe extern "C" fn dc_provider_json_from_domain(
pub unsafe extern "C" fn dc_provider_new_from_domain(
domain: *const libc::c_char,
) -> *mut libc::c_char {
let domain = to_string_lossy(domain);
match deltachat_provider_database::get_provider_info(&domain) {
Some(provider) => serde_json::to_string(provider)
.unwrap_or("".to_owned())
.strdup(),
None => "".strdup(),
) -> *const dc_provider_t {
match deltachat_provider_database::get_provider_info(&to_string_lossy(domain)) {
Some(provider) => provider,
None => ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_json_from_email(
pub unsafe extern "C" fn dc_provider_new_from_email(
email: *const libc::c_char,
) -> *mut libc::c_char {
) -> *const dc_provider_t {
let email = to_string_lossy(email);
let domain = deltachat_provider_database::get_domain_from_email(&email);
match deltachat_provider_database::get_provider_info(domain) {
Some(provider) => serde_json::to_string(provider)
.unwrap_or("".to_owned())
.strdup(),
None => "".strdup(),
Some(provider) => provider,
None => ptr::null(),
}
}
macro_rules! null_guard {
($context:tt) => {
if $context.is_null() {
return ptr::null_mut() as *mut libc::c_char;
}
};
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_overview_page(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
format!(
"{}/{}",
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
(*provider).overview_page
)
.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
null_guard!(provider);
(*provider).name.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_markdown(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
(*provider).markdown.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_status_date(
provider: *const dc_provider_t,
) -> *mut libc::c_char {
null_guard!(provider);
(*provider).status.date.strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
if provider.is_null() {
return 0;
}
match (*provider).status.state {
StatusState::OK => 1,
StatusState::PREPARATION => 2,
StatusState::BROKEN => 3,
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
()
}
// TODO expose general provider overview url?

View File

@@ -491,7 +491,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
println!("{:#?}", context.get_info());
}
"interrupt" => {
interrupt_inbox_idle(context);
interrupt_inbox_idle(context, true);
}
"maybenetwork" => {
maybe_network(context);

View File

@@ -202,7 +202,7 @@ fn stop_threads(context: &Context) {
println!("Stopping threads");
IS_RUNNING.store(false, Ordering::Relaxed);
interrupt_inbox_idle(context);
interrupt_inbox_idle(context, true);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
interrupt_smtp_idle(context);

View File

@@ -104,7 +104,7 @@ fn main() {
println!("stopping threads");
*running.write().unwrap() = false;
deltachat::job::interrupt_inbox_idle(&ctx);
deltachat::job::interrupt_inbox_idle(&ctx, true);
deltachat::job::interrupt_smtp_idle(&ctx);
println!("joining");

View File

@@ -1,8 +1,7 @@
"""Provider info class."""
from .capi import lib
from .capi import ffi, lib
from .cutil import as_dc_charpointer, from_dc_charpointer
import json
class ProviderNotFoundError(Exception):
@@ -17,12 +16,13 @@ class Provider(object):
"""
def __init__(self, domain):
provider = from_dc_charpointer(
lib.dc_provider_json_from_domain(as_dc_charpointer(domain))
provider = ffi.gc(
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
lib.dc_provider_unref,
)
if provider == "":
if provider == ffi.NULL:
raise ProviderNotFoundError("Provider not found")
self._provider = json.loads(provider)
self._provider = provider
@classmethod
def from_email(cls, email):
@@ -35,30 +35,33 @@ class Provider(object):
@property
def overview_page(self):
"""URL to the overview page of the provider on providers.delta.chat."""
return "https://providers.delta.chat/" + self._provider['overview_page']
return from_dc_charpointer(
lib.dc_provider_get_overview_page(self._provider))
@property
def name(self):
"""The name of the provider."""
return self._provider['name']
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
@property
def markdown(self):
"""Content of the information page, formatted as markdown."""
return self._provider['markdown']
return from_dc_charpointer(
lib.dc_provider_get_markdown(self._provider))
@property
def status_date(self):
"""The date the provider info was last updated, as a string."""
return self._provider['status']['date']
return from_dc_charpointer(
lib.dc_provider_get_status_date(self._provider))
@property
def status(self):
"""The status of the provider information.
This is
:attr:`"OK"`,
:attr:`"PREPARATION"` or
:attr:`"BROKEN"`.
This is one of the
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
"""
return self._provider['status']['state']
return lib.dc_provider_get_status(self._provider)

View File

@@ -1209,15 +1209,6 @@ class TestGroupStressTests:
print("chat is", msg.chat)
assert len(msg.chat.get_contacts()) == 4
lp.sec("ac3: checking that 'ac4' is a known contact")
ac3 = accounts[1]
msg3 = ac3.wait_next_incoming_message()
assert msg3.text == "hello"
contacts = ac3.get_contacts()
assert len(contacts) == 3
ac4_contacts = ac3.get_contacts(query=accounts[2].get_config("addr"))
assert len(ac4_contacts) == 1
lp.sec("ac1: removing one contacts and checking things are right")
to_remove = msg.chat.get_contacts()[-1]
msg.chat.remove_contact(to_remove)

View File

@@ -3,7 +3,6 @@ from deltachat import capi, cutil, const, set_context_callback, clear_context_ca
from deltachat.capi import ffi
from deltachat.capi import lib
from deltachat.account import EventLogger
import json
def test_empty_context():
@@ -104,23 +103,18 @@ def test_get_special_message_id_returns_empty_message(acfactory):
def test_provider_info():
provider_json = cutil.from_dc_charpointer(
lib.dc_provider_json_from_email(cutil.as_dc_charpointer("ex@example.com"))
)
provider = json.loads(provider_json)
assert provider['overview_page'] == "example.com"
assert provider['name'] == "Example"
assert provider['markdown'] == "\n..."
assert provider['status']['date'] == "2018-09"
assert provider['status']['state'] == "PREPARATION"
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
assert cutil.from_dc_charpointer(
lib.dc_provider_get_overview_page(provider)
) == "https://providers.delta.chat/example.com"
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():
provider_json = cutil.from_dc_charpointer(
lib.dc_provider_json_from_email(cutil.as_dc_charpointer("email@unexistent.no"))
)
assert provider_json == ""
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_closed():

View File

@@ -1,5 +1,6 @@
import pytest
from deltachat import const
from deltachat import provider
@@ -9,7 +10,7 @@ def test_provider_info_from_email():
assert example.name == "Example"
assert example.markdown == "\n..."
assert example.status_date == "2018-09"
assert example.status == "PREPARATION"
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_from_domain():
@@ -18,7 +19,7 @@ def test_provider_info_from_domain():
assert example.name == "Example"
assert example.markdown == "\n..."
assert example.status_date == "2018-09"
assert example.status == "PREPARATION"
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
def test_provider_info_none():

View File

@@ -144,7 +144,7 @@ impl Context {
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value);
interrupt_inbox_idle(self);
interrupt_inbox_idle(self, true);
ret
}
Config::SentboxWatch => {

View File

@@ -114,7 +114,7 @@ pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
/// approx. max. length returned by dc_msg_get_text()
const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. length returned by dc_get_msg_info()
const DC_MAX_GET_INFO_LEN: usize = 100_000;
const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;

View File

@@ -100,11 +100,11 @@ pub enum Origin {
/// address is in our address book
AdressBook = 0x80000,
/// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() !
SecurejoinInvited = 0x0100_0000,
SecurejoinInvited = 0x1000000,
/// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling dc_contact_is_verified() !
SecurejoinJoined = 0x0200_0000,
SecurejoinJoined = 0x2000000,
/// contact added mannually by dc_create_contact(), this should be the largets origin as otherwise the user cannot modify the names
ManuallyCreated = 0x0400_0000,
ManuallyCreated = 0x4000000,
}
impl Default for Origin {
@@ -114,11 +114,14 @@ impl Default for Origin {
}
impl Origin {
/// Contacts that are known, i. e. they came in via accepted contacts or
/// themselves an accepted contact. Known contacts are shown in the
/// contact list when one creates a chat and wants to add members etc.
pub fn is_known(self) -> bool {
self >= Origin::IncomingReplyTo
/// Contacts that are verified and known not to be spam.
pub fn is_verified(self) -> bool {
self as i32 >= 0x100
}
/// Contacts that are shown in the contact list.
pub fn include_in_contactlist(self) -> bool {
self as i32 >= DC_ORIGIN_MIN_CONTACT_LIST
}
}
@@ -398,7 +401,6 @@ impl Contact {
{
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
sth_modified = Modifier::Created;
info!(context, "added contact id={} addr={}", row_id, addr);
} else {
error!(context, "Cannot add contact.");
}
@@ -484,7 +486,7 @@ impl Contact {
params![
self_addr,
DC_CONTACT_ID_LAST_SPECIAL as i32,
Origin::IncomingReplyTo,
0x100,
&s3str_like_cmd,
&s3str_like_cmd,
if flag_verified_only { 0 } else { 1 },

View File

@@ -61,6 +61,9 @@ pub fn dc_receive_imf(
ensure!(mime_parser.has_headers(), "No Headers Found");
// the function returns the number of created messages in the database
let mut incoming = true;
let mut incoming_origin = Origin::Unknown;
let mut to_id = 0u32;
let mut chat_id = 0;
let mut hidden = false;
@@ -71,6 +74,8 @@ pub fn dc_receive_imf(
let mut created_db_entries = Vec::new();
let mut create_event_to_send = Some(CreateEvent::MsgsChanged);
let mut to_ids = ContactIds::new();
// helper method to handle early exit and memory cleanup
let cleanup = |context: &Context,
create_event_to_send: &Option<CreateEvent>,
@@ -97,31 +102,48 @@ pub fn dc_receive_imf(
sent_timestamp = mailparse::dateparse(value).unwrap_or_default();
}
// Make sure, to_ids starts with the first To:-address (Cc: is added in the loop below pass)
if let Some(field) = mime_parser.get(HeaderDef::To) {
dc_add_or_lookup_contacts_by_address_list(
context,
&field,
if !incoming {
Origin::OutgoingTo
} else if incoming_origin.is_verified() {
Origin::IncomingTo
} else {
Origin::IncomingUnknownTo
},
&mut to_ids,
)?;
}
// get From: (it can be an address list!) and check if it is known (for known From:'s we add
// the other To:/Cc: in the 3rd pass)
// or if From: is equal to SELF (in this case, it is any outgoing messages,
// we do not check Return-Path any more as this is unreliable, see issue #150)
let mut from_id = 0;
let mut from_id_blocked = false;
let mut incoming = true;
let mut incoming_origin = Origin::Unknown;
if let Some(field_from) = mime_parser.get(HeaderDef::From_) {
let from_ids = dc_add_or_lookup_contacts_by_address_list(
let mut from_ids = ContactIds::new();
dc_add_or_lookup_contacts_by_address_list(
context,
&field_from,
Origin::IncomingUnknownFrom,
&mut from_ids,
)?;
if from_ids.len() > 1 {
warn!(
context,
"mail has more than one From address, only using first: {:?}", field_from
"mail has more than one address in From: {:?}", field_from
);
}
if from_ids.contains(&DC_CONTACT_ID_SELF) {
incoming = false;
from_id = DC_CONTACT_ID_SELF;
incoming_origin = Origin::OutgoingBcc;
if to_ids.len() == 1 && to_ids.contains(&DC_CONTACT_ID_SELF) {
from_id = DC_CONTACT_ID_SELF;
}
} else if !from_ids.is_empty() {
from_id = from_ids.get_index(0).cloned().unwrap_or_default();
incoming_origin = Contact::get_origin_by_id(context, from_id, &mut from_id_blocked)
@@ -133,23 +155,6 @@ pub fn dc_receive_imf(
}
}
let mut to_ids = ContactIds::new();
for header_def in &[HeaderDef::To, HeaderDef::Cc] {
if let Some(field) = mime_parser.get(header_def.clone()) {
to_ids.extend(&dc_add_or_lookup_contacts_by_address_list(
context,
&field,
if !incoming {
Origin::OutgoingTo
} else if incoming_origin.is_known() {
Origin::IncomingTo
} else {
Origin::IncomingUnknownTo
},
)?);
}
}
// Add parts
let rfc724_mid = match mime_parser.get_rfc724_mid() {
@@ -172,16 +177,17 @@ pub fn dc_receive_imf(
&mut mime_parser,
imf_raw,
incoming,
incoming_origin,
&mut incoming_origin,
server_folder.as_ref(),
server_uid,
&to_ids,
&mut to_ids,
&rfc724_mid,
&mut sent_timestamp,
from_id,
&mut from_id,
from_id_blocked,
&mut hidden,
&mut chat_id,
&mut to_id,
flags,
&mut needs_delete_job,
&mut insert_msg_id,
@@ -251,16 +257,17 @@ fn add_parts(
mut mime_parser: &mut MimeParser,
imf_raw: &[u8],
incoming: bool,
incoming_origin: Origin,
incoming_origin: &mut Origin,
server_folder: impl AsRef<str>,
server_uid: u32,
to_ids: &ContactIds,
to_ids: &mut ContactIds,
rfc724_mid: &str,
sent_timestamp: &mut i64,
from_id: u32,
from_id: &mut u32,
from_id_blocked: bool,
hidden: &mut bool,
chat_id: &mut u32,
to_id: &mut u32,
flags: u32,
needs_delete_job: &mut bool,
insert_msg_id: &mut MsgId,
@@ -274,7 +281,24 @@ fn add_parts(
let mut rcvd_timestamp = 0;
let mut mime_in_reply_to = String::new();
let mut mime_references = String::new();
let mut incoming_origin = incoming_origin;
// collect the rest information, CC: is added to the to-list, BCC: is ignored
// (we should not add BCC to groups as this would split groups. We could add them as "known contacts",
// however, the benefit is very small and this may leak data that is expected to be hidden)
if let Some(fld_cc) = mime_parser.get(HeaderDef::Cc) {
dc_add_or_lookup_contacts_by_address_list(
context,
fld_cc,
if !incoming {
Origin::OutgoingCc
} else if incoming_origin.is_verified() {
Origin::IncomingCc
} else {
Origin::IncomingUnknownCc
},
to_ids,
)?;
}
// 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
@@ -314,15 +338,13 @@ fn add_parts(
// - outgoing messages introduce a chat with the first to: address if they are sent by a messenger
// - incoming messages introduce a chat only for known contacts if they are sent by a messenger
// (of course, the user can add other chats manually later)
let to_id: u32;
if incoming {
state = if 0 != flags & DC_IMAP_SEEN {
MessageState::InSeen
} else {
MessageState::InFresh
};
to_id = DC_CONTACT_ID_SELF;
*to_id = DC_CONTACT_ID_SELF;
let mut needs_stop_ongoing_process = false;
// handshake messages must be processed _before_ chats are created
@@ -332,7 +354,7 @@ fn add_parts(
msgrmsg = 1;
*chat_id = 0;
allow_creation = true;
match handle_securejoin_handshake(context, mime_parser, from_id) {
match handle_securejoin_handshake(context, mime_parser, *from_id) {
Ok(ret) => {
if ret.hide_this_msg {
*hidden = true;
@@ -354,7 +376,7 @@ fn add_parts(
}
let (test_normal_chat_id, test_normal_chat_id_blocked) =
chat::lookup_by_contact_id(context, from_id).unwrap_or_default();
chat::lookup_by_contact_id(context, *from_id).unwrap_or_default();
// get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list,
// it might also be blocked and displayed in the deaddrop as a result
@@ -374,7 +396,7 @@ fn add_parts(
&mut mime_parser,
allow_creation,
create_blocked,
from_id,
*from_id,
to_ids,
)?;
*chat_id = new_chat_id;
@@ -395,7 +417,7 @@ fn add_parts(
if *chat_id == 0 {
// try to create a normal chat
let create_blocked = if from_id == to_id {
let create_blocked = if *from_id == *to_id {
Blocked::Not
} else {
Blocked::Deaddrop
@@ -406,7 +428,7 @@ fn add_parts(
chat_id_blocked = test_normal_chat_id_blocked;
} else if allow_creation {
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, from_id, create_blocked)
chat::create_or_lookup_by_contact_id(context, *from_id, create_blocked)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
@@ -418,13 +440,13 @@ fn add_parts(
} else if is_reply_to_known_message(context, mime_parser) {
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
// the contact requests will pop up and this should be just fine.
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo);
Contact::scaleup_origin_by_id(context, *from_id, Origin::IncomingReplyTo);
info!(
context,
"Message is a reply to a known message, mark sender as known.",
);
if !incoming_origin.is_known() {
incoming_origin = Origin::IncomingReplyTo;
if !incoming_origin.is_verified() {
*incoming_origin = Origin::IncomingReplyTo;
}
}
}
@@ -439,7 +461,7 @@ fn add_parts(
// to not result in a chatlist-contact-request (this would require the state FRESH)
if Blocked::Not != chat_id_blocked
&& state == MessageState::InFresh
&& !incoming_origin.is_known()
&& !incoming_origin.is_verified()
&& msgrmsg == 0
&& show_emails != ShowEmails::All
{
@@ -458,15 +480,16 @@ 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;
to_id = to_ids.get_index(0).cloned().unwrap_or_default();
*from_id = DC_CONTACT_ID_SELF;
if !to_ids.is_empty() {
*to_id = to_ids.get_index(0).cloned().unwrap_or_default();
if *chat_id == 0 {
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
context,
&mut mime_parser,
allow_creation,
Blocked::Not,
from_id,
*from_id,
to_ids,
)?;
*chat_id = new_chat_id;
@@ -478,13 +501,14 @@ fn add_parts(
}
}
if *chat_id == 0 && allow_creation {
let create_blocked = if 0 != msgrmsg && !Contact::is_blocked_load(context, to_id) {
let create_blocked = if 0 != msgrmsg && !Contact::is_blocked_load(context, *to_id) {
Blocked::Not
} else {
Blocked::Deaddrop
};
let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked)
.unwrap_or_default();
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, *to_id, create_blocked)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
@@ -497,7 +521,7 @@ fn add_parts(
}
}
}
let self_sent = from_id == DC_CONTACT_ID_SELF
let self_sent = *from_id == DC_CONTACT_ID_SELF
&& to_ids.len() == 1
&& to_ids.contains(&DC_CONTACT_ID_SELF);
@@ -524,7 +548,7 @@ fn add_parts(
calc_timestamps(
context,
*chat_id,
from_id,
*from_id,
*sent_timestamp,
0 == flags & DC_IMAP_SEEN,
&mut sort_timestamp,
@@ -588,8 +612,8 @@ fn add_parts(
server_folder.as_ref(),
server_uid as i32,
*chat_id as i32,
from_id as i32,
to_id as i32,
*from_id as i32,
*to_id as i32,
sort_timestamp,
*sent_timestamp,
rcvd_timestamp,
@@ -1513,7 +1537,8 @@ fn dc_add_or_lookup_contacts_by_address_list(
context: &Context,
addr_list_raw: &str,
origin: Origin,
) -> Result<ContactIds> {
to_ids: &mut ContactIds,
) -> Result<()> {
let addrs = match mailparse::addrparse(addr_list_raw) {
Ok(addrs) => addrs,
Err(err) => {
@@ -1521,11 +1546,14 @@ fn dc_add_or_lookup_contacts_by_address_list(
}
};
let mut contact_ids = ContactIds::new();
info!(
context,
"dc_add_or_lookup_contacts_by_address raw={:?} addrs={:?}", addr_list_raw, addrs
);
for addr in addrs.iter() {
match addr {
mailparse::MailAddr::Single(info) => {
contact_ids.insert(add_or_lookup_contact_by_addr(
to_ids.insert(add_or_lookup_contact_by_addr(
context,
&info.display_name,
&info.addr,
@@ -1534,7 +1562,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
}
mailparse::MailAddr::Group(infos) => {
for info in &infos.addrs {
contact_ids.insert(add_or_lookup_contact_by_addr(
to_ids.insert(add_or_lookup_contact_by_addr(
context,
&info.display_name,
&info.addr,
@@ -1545,7 +1573,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
}
}
Ok(contact_ids)
Ok(())
}
/// Add contacts to database on receiving messages.
@@ -1563,6 +1591,10 @@ fn add_or_lookup_contact_by_addr(
.map(normalize_name)
.unwrap_or_default();
info!(
context,
"looking up addr={:?} display_name={:?}", addr, display_name_normalized
);
let (row_id, _modified) =
Contact::add_or_lookup(context, display_name_normalized, addr, origin)?;
ensure!(row_id > 0, "could not add contact: {:?}", addr);

View File

@@ -49,8 +49,8 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
/// - harmonize together while being different enough
/// (therefore, we cannot just use random rgb colors :)
const COLORS: [u32; 16] = [
0xe5_65_55, 0xf2_8c_48, 0x8e_85_ee, 0x76_c8_4d, 0x5b_b6_cc, 0x54_9c_dd, 0xd2_5c_99, 0xb3_78_00,
0xf2_30_30, 0x39_b2_49, 0xbb_24_3b, 0x96_40_78, 0x66_87_4f, 0x30_8a_b9, 0x12_7e_d0, 0xbe_45_0c,
0xe56555, 0xf28c48, 0x8e85ee, 0x76c84d, 0x5bb6cc, 0x549cdd, 0xd25c99, 0xb37800, 0xf23030,
0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c,
];
pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
@@ -59,7 +59,7 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
let bytes = str_lower.as_bytes();
for (i, byte) in bytes.iter().enumerate() {
checksum += (i + 1) * *byte as usize;
checksum %= 0x00ff_ffff;
checksum %= 0xffffff;
}
let color_index = checksum % COLORS.len();

View File

@@ -139,12 +139,13 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
dehtml.add_text = AddText::YesPreserveLineEnds;
}
"a" => {
if let Some(href) = event
.html_attributes()
.filter_map(|attr| attr.ok())
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "href")
{
if let Some(href) = event.html_attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "href")
.unwrap_or_default()
}) {
let href = href
.unwrap()
.unescape_and_decode_value(reader)
.unwrap_or_default()
.to_lowercase();

View File

@@ -480,19 +480,19 @@ pub fn perform_sentbox_idle(context: &Context) {
.idle(context, use_network);
}
pub fn interrupt_inbox_idle(context: &Context) {
info!(context, "interrupt_inbox_idle called");
// we do not block on trying to obtain the thread lock
// because we don't know in which state the thread is.
// If it's currently fetching then we can not get the lock
// but we flag it for checking jobs so that idle will be skipped.
match context.inbox_thread.try_read() {
Ok(inbox_thread) => {
inbox_thread.interrupt_idle(context);
}
Err(err) => {
*context.perform_inbox_jobs_needed.write().unwrap() = true;
warn!(context, "could not interrupt idle: {}", err);
pub fn interrupt_inbox_idle(context: &Context, block: bool) {
info!(context, "interrupt_inbox_idle called blocking={}", block);
if block {
context.inbox_thread.read().unwrap().interrupt_idle(context);
} else {
match context.inbox_thread.try_read() {
Ok(inbox_thread) => {
inbox_thread.interrupt_idle(context);
}
Err(err) => {
*context.perform_inbox_jobs_needed.write().unwrap() = true;
warn!(context, "could not interrupt idle: {}", err);
}
}
}
}
@@ -604,7 +604,7 @@ pub fn maybe_network(context: &Context) {
}
interrupt_smtp_idle(context);
interrupt_inbox_idle(context);
interrupt_inbox_idle(context, true);
interrupt_mvbox_idle(context);
interrupt_sentbox_idle(context);
}
@@ -972,7 +972,7 @@ pub fn job_add(
).ok();
match thread {
Thread::Imap => interrupt_inbox_idle(context),
Thread::Imap => interrupt_inbox_idle(context, false),
Thread::Smtp => interrupt_smtp_idle(context),
Thread::Unknown => {}
}

View File

@@ -1,7 +1,7 @@
#![deny(clippy::correctness, missing_debug_implementations, clippy::all)]
// for now we hide warnings to not clutter/hide errors during "cargo clippy"
#![allow(clippy::cognitive_complexity, clippy::too_many_arguments)]
#![allow(clippy::match_bool)]
#![allow(clippy::unreadable_literal, clippy::match_bool)]
#![feature(ptr_wrapping_offset_from)]
#![feature(drain_filter)]

View File

@@ -8,8 +8,9 @@ macro_rules! info {
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let thread = ::std::thread::current();
let full = format!("{thid:?} {file}:{line}: {msg}",
let full = format!("{thid:?}/{thname} {file}:{line}: {msg}",
thid = thread.id(),
thname = thread.name().unwrap_or("unnamed"),
file = file!(),
line = line!(),
msg = &formatted);
@@ -25,8 +26,9 @@ macro_rules! warn {
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let thread = ::std::thread::current();
let full = format!("{thid:?} {file}:{line}: {msg}",
let full = format!("{thid:?}/{thname} {file}:{line}: {msg}",
thid = thread.id(),
thname = thread.name().unwrap_or("unnamed"),
file = file!(),
line = line!(),
msg = &formatted);

View File

@@ -720,7 +720,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
return ret;
}
let rawtxt = rawtxt.unwrap_or_default();
let rawtxt = dc_truncate(rawtxt.trim(), 100_000, false);
let rawtxt = dc_truncate(rawtxt.trim(), 100000, false);
let fts = dc_timestamp_to_str(msg.get_timestamp());
ret += &format!("Sent: {}", fts);

View File

@@ -2,20 +2,17 @@
pub mod send;
use std::time::Duration;
use async_smtp::smtp::client::net::*;
use async_smtp::*;
use async_std::task;
use crate::constants::*;
use crate::context::Context;
use crate::events::Event;
use crate::login_param::{dc_build_tls, LoginParam};
use crate::oauth2::*;
/// SMTP write and read timeout in seconds.
const SMTP_TIMEOUT: u64 = 30;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Bad parameters")]
@@ -24,12 +21,12 @@ pub enum Error {
InvalidLoginAddress {
address: String,
#[cause]
error: error::Error,
error: async_smtp::error::Error,
},
#[fail(display = "SMTP failed to connect: {:?}", _0)]
ConnectionFailure(#[cause] smtp::error::Error),
ConnectionFailure(#[cause] async_smtp::smtp::error::Error),
#[fail(display = "SMTP: failed to setup connection {:?}", _0)]
ConnectionSetupFailure(#[cause] smtp::error::Error),
ConnectionSetupFailure(#[cause] async_smtp::smtp::error::Error),
#[fail(display = "SMTP: oauth2 error {:?}", _0)]
Oauth2Error { address: String },
#[fail(display = "TLS error")]
@@ -47,7 +44,7 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Default, DebugStub)]
pub struct Smtp {
#[debug_stub(some = "SmtpTransport")]
transport: Option<smtp::SmtpTransport>,
transport: Option<async_smtp::smtp::SmtpTransport>,
/// Email address we are sending from.
from: Option<EmailAddress>,
}
@@ -60,25 +57,18 @@ impl Smtp {
/// Disconnect the SMTP transport and drop it entirely.
pub fn disconnect(&mut self) {
if let Some(mut transport) = self.transport.take() {
async_std::task::block_on(transport.close()).ok();
if let Some(ref mut transport) = self.transport.take() {
transport.close();
}
}
/// Check whether we are connected.
/// check whether we are connected
pub fn is_connected(&self) -> bool {
self.transport
.as_ref()
.map(|t| t.is_connected())
.unwrap_or_default()
self.transport.is_some()
}
/// Connect using the provided login params.
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> {
async_std::task::block_on(self.inner_connect(context, lp))
}
async fn inner_connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return Ok(());
@@ -114,21 +104,21 @@ impl Smtp {
}
let user = &lp.send_user;
(
smtp::authentication::Credentials::new(
async_smtp::smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![smtp::authentication::Mechanism::Xoauth2],
vec![async_smtp::smtp::authentication::Mechanism::Xoauth2],
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
(
smtp::authentication::Credentials::new(user, pw),
async_smtp::smtp::authentication::Credentials::new(user, pw),
vec![
smtp::authentication::Mechanism::Plain,
smtp::authentication::Mechanism::Login,
async_smtp::smtp::authentication::Mechanism::Plain,
async_smtp::smtp::authentication::Mechanism::Login,
],
)
};
@@ -136,31 +126,30 @@ impl Smtp {
let security = if 0
!= lp.server_flags & (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_PLAIN) as i32
{
smtp::ClientSecurity::Opportunistic(tls_parameters)
async_smtp::smtp::ClientSecurity::Opportunistic(tls_parameters)
} else {
smtp::ClientSecurity::Wrapper(tls_parameters)
async_smtp::smtp::ClientSecurity::Wrapper(tls_parameters)
};
let client = smtp::SmtpClient::with_security((domain.as_str(), port), security)
.await
.map_err(Error::ConnectionSetupFailure)?;
let client = task::block_on(async_smtp::smtp::SmtpClient::with_security(
(domain.as_str(), port),
security,
))
.map_err(Error::ConnectionSetupFailure)?;
let client = client
.smtp_utf8(true)
.credentials(creds)
.authentication_mechanism(mechanism)
.connection_reuse(smtp::ConnectionReuseParameters::ReuseUnlimited)
.timeout(Some(Duration::from_secs(SMTP_TIMEOUT)));
.connection_reuse(async_smtp::smtp::ConnectionReuseParameters::ReuseUnlimited);
let mut trans = client.into_transport();
trans.connect().await.map_err(Error::ConnectionFailure)?;
task::block_on(trans.connect()).map_err(Error::ConnectionFailure)?;
self.transport = Some(trans);
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
Ok(())
}
}

View File

@@ -1,11 +1,16 @@
//! # SMTP message sending
use std::time::Duration;
use super::Smtp;
use async_smtp::*;
use crate::context::Context;
use crate::events::Event;
/// SMTP send times out after 15 minutes
const SEND_TIMEOUT: u64 = 15 * 60;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Fail)]
@@ -51,15 +56,18 @@ impl Smtp {
format!("{}", job_id), // only used for internal logging
message,
);
if let Some(ref mut transport) = self.transport {
transport.send(mail).await.map_err(Error::SendError)?;
let res =
async_std::future::timeout(Duration::from_secs(SEND_TIMEOUT), transport.send(mail))
.await?
.map_err(Error::SendError);
context.call_cb(Event::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len, recipients_display
)));
Ok(())
res.map(|_response| {
context.call_cb(Event::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len, recipients_display
)));
})
} else {
warn!(
context,