mirror of
https://github.com/chatmail/core.git
synced 2026-04-15 12:36:30 +03:00
Compare commits
2 Commits
disable-wa
...
1.30.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bb830b696 | ||
|
|
94e7954980 |
@@ -7,10 +7,6 @@ executors:
|
||||
doxygen:
|
||||
docker:
|
||||
- image: hrektts/doxygen
|
||||
python:
|
||||
docker:
|
||||
- image: 3.7.7-stretch
|
||||
|
||||
|
||||
restore-workspace: &restore-workspace
|
||||
attach_workspace:
|
||||
|
||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,37 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.33.0
|
||||
|
||||
- let `dc_set_muted()` also mute one-to-one chats #1470
|
||||
|
||||
- fix a bug that led to load and traffic if the server does not use sent-folder
|
||||
#1472
|
||||
|
||||
|
||||
## 1.32.0
|
||||
|
||||
- fix endless loop when trying to download messages with bad RFC Message-ID,
|
||||
also be more reliable on similar errors #1463 #1466 #1462
|
||||
|
||||
- fix bug with comma in contact request #1438
|
||||
|
||||
- do not refer to hidden messages on replies #1459
|
||||
|
||||
- improve error handling #1468 #1465 #1464
|
||||
|
||||
|
||||
## 1.31.0
|
||||
|
||||
- always describe the context of the displayed error #1451
|
||||
|
||||
- do not emit `DC_EVENT_ERROR` when message sending fails;
|
||||
`dc_msg_get_state()` and `dc_get_msg_info()` are sufficient #1451
|
||||
|
||||
- new config-option `media_quality` #1449
|
||||
|
||||
- try over if writing message to database fails #1447
|
||||
|
||||
|
||||
## 1.30.0
|
||||
|
||||
- expunge deleted messages #1440
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -629,7 +629,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.33.0"
|
||||
version = "1.30.0"
|
||||
dependencies = [
|
||||
"anyhow 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-imap 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -695,10 +695,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.33.0"
|
||||
version = "1.30.0"
|
||||
dependencies = [
|
||||
"anyhow 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deltachat 1.33.0",
|
||||
"deltachat 1.30.0",
|
||||
"human-panic 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.33.0"
|
||||
version = "1.30.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -37,7 +37,7 @@ echo -----------------------
|
||||
# Bundle external shared libraries into the wheels
|
||||
pushd $WHEELHOUSEDIR
|
||||
|
||||
pip3 install -U pip setuptools
|
||||
pip3 install -U pip
|
||||
pip3 install devpi-client
|
||||
devpi use https://m.devpi.net
|
||||
devpi login dc --password $DEVPI_LOGIN
|
||||
|
||||
@@ -48,7 +48,7 @@ def run():
|
||||
projectnames = get_projectnames(baseurl, username, indexname)
|
||||
if indexname == "master" or not indexname:
|
||||
continue
|
||||
clear_index = not projectnames
|
||||
assert projectnames == ["deltachat"]
|
||||
for projectname in projectnames:
|
||||
dates = get_release_dates(baseurl, username, indexname, projectname)
|
||||
if not dates:
|
||||
@@ -60,11 +60,8 @@ def run():
|
||||
date = datetime.datetime(*max(dates))
|
||||
if (datetime.datetime.now() - date) > datetime.timedelta(days=MAXDAYS):
|
||||
assert username and indexname
|
||||
clear_index = True
|
||||
break
|
||||
if clear_index:
|
||||
url = baseurl + username + "/" + indexname
|
||||
subprocess.check_call(["devpi", "index", "-y", "--delete", url])
|
||||
url = baseurl + username + "/" + indexname
|
||||
subprocess.check_call(["devpi", "index", "-y", "--delete", url])
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.33.0"
|
||||
version = "1.30.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -378,21 +378,10 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* >=1=seconds, after which messages are deleted automatically from the device.
|
||||
* Messages in the "saved messages" chat (see dc_chat_is_self_talk()) are skipped.
|
||||
* Messages are deleted whether they were seen or not, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `delete_server_after` = 0=do not delete messages from server automatically (default),
|
||||
* 1=delete messages directly after receiving from server, mvbox is skipped.
|
||||
* >1=seconds, after which messages are deleted automatically from the server, mvbox is used as defined.
|
||||
* >=1=seconds, after which messages are deleted automatically from the server.
|
||||
* "Saved messages" are deleted from the server as well as
|
||||
* emails matching the `show_emails` settings above, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `media_quality` = DC_MEDIA_QUALITY_BALANCED (0) =
|
||||
* good outgoing images/videos/voice quality at reasonable sizes (default)
|
||||
* DC_MEDIA_QUALITY_WORSE (1)
|
||||
* allow worse images/videos/voice quality to gain smaller sizes,
|
||||
* suitable for providers or areas known to have a bad connection.
|
||||
* In contrast to other options, the implementation of this option is currently up to the UIs;
|
||||
* this may change in future, however,
|
||||
* having the option in the core allows provider-specific-defaults already today.
|
||||
*
|
||||
* If you want to retrieve a value, use dc_get_config().
|
||||
*
|
||||
@@ -960,10 +949,8 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha
|
||||
* if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
||||
* the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
|
||||
* chats
|
||||
* - the flag DC_GCL_FOR_FORWARDING sorts "Saved messages" to the top of the chatlist
|
||||
* and hides the "Device chat" and the deaddrop.
|
||||
* - the flag DC_GCL_FOR_FORWARDING sorts "Saved messages" to the top of the chatlist,
|
||||
* typically used on forwarding, may be combined with DC_GCL_NO_SPECIALS
|
||||
* to also hide the archive link.
|
||||
* - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
||||
* to the list (may be used eg. for selecting chats on forwarding, the flag is
|
||||
* not needed when DC_GCL_ARCHIVED_ONLY is already set)
|
||||
@@ -1323,7 +1310,6 @@ int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t ch
|
||||
* by the dc_set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
* This is typically used to show the estimated impact to the user before actually enabling ephemeral messages.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param from_server 1=Estimate deletion count for server, 0=Estimate deletion count for device
|
||||
* @param seconds Count messages older than the given number of seconds.
|
||||
@@ -1640,7 +1626,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch
|
||||
/**
|
||||
* Set mute duration of a chat.
|
||||
*
|
||||
* The ui can then call dc_chat_is_muted() when receiving a new message to decide whether it should trigger an notification.
|
||||
* This value can be checked by the ui upon receiving a new message to decide whether it should trigger an notification.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED.
|
||||
*
|
||||
@@ -2978,7 +2964,7 @@ int dc_chat_is_sending_locations (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the chat is currently muted (can be changed by dc_set_chat_mute_duration()).
|
||||
* Check whether the chat is currently muted
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
* @param chat The chat object.
|
||||
@@ -4506,14 +4492,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_SHOW_EMAILS_ACCEPTED_CONTACTS 1
|
||||
#define DC_SHOW_EMAILS_ALL 2
|
||||
|
||||
|
||||
/*
|
||||
* Values for dc_get|set_config("media_quality")
|
||||
*/
|
||||
#define DC_MEDIA_QUALITY_BALANCED 0
|
||||
#define DC_MEDIA_QUALITY_WORSE 1
|
||||
|
||||
|
||||
/*
|
||||
* Values for dc_get|set_config("key_gen_type")
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,7 @@ def test_group_tracking_plugin(acfactory, lp):
|
||||
|
||||
botproc.fnmatch_lines("""
|
||||
*ac_chat_modified*bot test group*
|
||||
""")
|
||||
""".format(ac1.get_config("addr")))
|
||||
|
||||
lp.sec("adding third member {}".format(ac2.get_config("addr")))
|
||||
contact3 = ac1.create_contact(ac2.get_config("addr"))
|
||||
|
||||
@@ -30,7 +30,7 @@ class Account(object):
|
||||
"""
|
||||
MissingCredentials = MissingCredentials
|
||||
|
||||
def __init__(self, db_path, os_name=None, logging=True):
|
||||
def __init__(self, db_path, os_name=None):
|
||||
""" initialize account object.
|
||||
|
||||
:param db_path: a path to the account database. The database
|
||||
@@ -39,7 +39,6 @@ class Account(object):
|
||||
"""
|
||||
# initialize per-account plugin system
|
||||
self._pm = hookspec.PerAccount._make_plugin_manager()
|
||||
self._logging = logging
|
||||
self.add_account_plugin(self)
|
||||
|
||||
self._dc_context = ffi.gc(
|
||||
@@ -64,14 +63,6 @@ class Account(object):
|
||||
atexit.register(self.shutdown)
|
||||
hook.dc_account_init(account=self)
|
||||
|
||||
def disable_logging(self):
|
||||
""" disable logging. """
|
||||
self._logging = False
|
||||
|
||||
def enable_logging(self):
|
||||
""" re-enable logging. """
|
||||
self._logging = True
|
||||
|
||||
@hookspec.account_hookimpl
|
||||
def ac_process_ffi_event(self, ffi_event):
|
||||
for name, kwargs in self._map_ffi_event(ffi_event):
|
||||
@@ -82,8 +73,7 @@ class Account(object):
|
||||
# self.shutdown()
|
||||
|
||||
def ac_log_line(self, msg):
|
||||
if self._logging:
|
||||
self._pm.hook.ac_log_line(message=msg)
|
||||
self._pm.hook.ac_log_line(message=msg)
|
||||
|
||||
def _check_config_key(self, name):
|
||||
if name not in self._configkeys:
|
||||
|
||||
@@ -5,13 +5,9 @@ import subprocess
|
||||
import queue
|
||||
import threading
|
||||
import fnmatch
|
||||
import time
|
||||
import weakref
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
import time
|
||||
from . import Account, const
|
||||
from .tracker import ConfigureTracker
|
||||
from .capi import lib
|
||||
@@ -19,7 +15,7 @@ from .eventlogger import FFIEventLogger, FFIEventTracker
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest._code import Source
|
||||
|
||||
import deltachat
|
||||
import tempfile
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -44,55 +40,11 @@ def pytest_configure(config):
|
||||
if cfg:
|
||||
config.option.liveconfig = cfg
|
||||
|
||||
# Make sure we don't get garbled output because threads keep running
|
||||
# collect all ever created accounts in a weakref-set (so we don't
|
||||
# keep objects unneccessarily alive) and enable/disable logging
|
||||
# for each pytest test phase # (setup/call/teardown).
|
||||
# Additionally make the acfactory use a logging/no-logging default.
|
||||
|
||||
class LoggingAspect:
|
||||
def __init__(self):
|
||||
self._accounts = weakref.WeakSet()
|
||||
|
||||
@deltachat.global_hookimpl
|
||||
def dc_account_init(self, account):
|
||||
self._accounts.add(account)
|
||||
|
||||
def disable_logging(self, item):
|
||||
for acc in self._accounts:
|
||||
acc.disable_logging()
|
||||
acfactory = item.funcargs.get("acfactory")
|
||||
if acfactory:
|
||||
acfactory.set_logging_default(False)
|
||||
|
||||
def enable_logging(self, item):
|
||||
for acc in self._accounts:
|
||||
acc.enable_logging()
|
||||
acfactory = item.funcargs.get("acfactory")
|
||||
if acfactory:
|
||||
acfactory.set_logging_default(True)
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_setup(self, item):
|
||||
self.enable_logging(item)
|
||||
yield
|
||||
self.disable_logging(item)
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
self.enable_logging(pyfuncitem)
|
||||
yield
|
||||
self.disable_logging(pyfuncitem)
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_teardown(self, item):
|
||||
self.enable_logging(item)
|
||||
yield
|
||||
self.disable_logging(item)
|
||||
|
||||
la = LoggingAspect()
|
||||
config.pluginmanager.register(la)
|
||||
deltachat.register_global_plugin(la)
|
||||
def pytest_runtest_setup(item):
|
||||
if (list(item.iter_markers(name="ignored"))
|
||||
and not item.config.getoption("ignored")):
|
||||
pytest.skip("Ignored tests not requested, use --ignored")
|
||||
|
||||
|
||||
def pytest_report_header(config, startdir):
|
||||
@@ -212,34 +164,24 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
|
||||
self.live_count = 0
|
||||
self.offline_count = 0
|
||||
self._finalizers = []
|
||||
self._accounts = []
|
||||
self.init_time = time.time()
|
||||
self._generated_keys = ["alice", "bob", "charlie",
|
||||
"dom", "elena", "fiona"]
|
||||
self.set_logging_default(False)
|
||||
|
||||
def finalize(self):
|
||||
while self._finalizers:
|
||||
fin = self._finalizers.pop()
|
||||
fin()
|
||||
|
||||
while self._accounts:
|
||||
acc = self._accounts.pop()
|
||||
acc.shutdown()
|
||||
acc.disable_logging()
|
||||
|
||||
def make_account(self, path, logid, quiet=False):
|
||||
ac = Account(path, logging=self._logging)
|
||||
ac = Account(path)
|
||||
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
|
||||
ac._configtracker = ac.add_account_plugin(ConfigureTracker())
|
||||
if not quiet:
|
||||
ac.add_account_plugin(FFIEventLogger(ac, logid=logid))
|
||||
self._accounts.append(ac)
|
||||
self._finalizers.append(ac.shutdown)
|
||||
return ac
|
||||
|
||||
def set_logging_default(self, logging):
|
||||
self._logging = bool(logging)
|
||||
|
||||
def get_unconfigured_account(self):
|
||||
self.offline_count += 1
|
||||
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
|
||||
|
||||
22
src/chat.rs
22
src/chat.rs
@@ -385,7 +385,7 @@ impl ChatId {
|
||||
let sql = &context.sql;
|
||||
let query = format!(
|
||||
"SELECT {} \
|
||||
FROM msgs WHERE chat_id=? AND state NOT IN (?, ?, ?, ?) AND NOT hidden \
|
||||
FROM msgs WHERE chat_id=? AND state NOT IN (?, ?, ?, ?) \
|
||||
ORDER BY timestamp DESC, id DESC \
|
||||
LIMIT 1;",
|
||||
fields
|
||||
@@ -1612,7 +1612,8 @@ pub fn delete_device_expired_messages(context: &Context) -> Result<bool, Error>
|
||||
WHERE timestamp < ? \
|
||||
AND chat_id > ? \
|
||||
AND chat_id != ? \
|
||||
AND chat_id != ?",
|
||||
AND chat_id != ? \
|
||||
AND NOT hidden",
|
||||
params![
|
||||
DC_CHAT_ID_TRASH,
|
||||
threshold_timestamp,
|
||||
@@ -2071,17 +2072,18 @@ impl rusqlite::types::FromSql for MuteDuration {
|
||||
|
||||
pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<(), Error> {
|
||||
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET muted_until=? WHERE id=?;",
|
||||
params![duration, chat_id],
|
||||
)
|
||||
.is_ok()
|
||||
if real_group_exists(context, chat_id)
|
||||
&& sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET muted_until=? WHERE id=?;",
|
||||
params![duration, chat_id],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
} else {
|
||||
bail!("Failed to set mute duration, chat might not exist -");
|
||||
bail!("Failed to set name");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -92,11 +92,6 @@ impl Chatlist {
|
||||
query: Option<&str>,
|
||||
query_contact_id: Option<u32>,
|
||||
) -> Result<Self> {
|
||||
let flag_archived_only = 0 != listflags & DC_GCL_ARCHIVED_ONLY;
|
||||
let flag_for_forwarding = 0 != listflags & DC_GCL_FOR_FORWARDING;
|
||||
let flag_no_specials = 0 != listflags & DC_GCL_NO_SPECIALS;
|
||||
let flag_add_alldone_hint = 0 != listflags & DC_GCL_ADD_ALLDONE_HINT;
|
||||
|
||||
// Note that we do not emit DC_EVENT_MSGS_MODIFIED here even if some
|
||||
// messages get deleted to avoid reloading the same chatlist.
|
||||
if let Err(err) = delete_device_expired_messages(context) {
|
||||
@@ -116,7 +111,7 @@ impl Chatlist {
|
||||
.map_err(Into::into)
|
||||
};
|
||||
|
||||
let skip_id = if flag_for_forwarding {
|
||||
let skip_id = if 0 != listflags & DC_GCL_FOR_FORWARDING {
|
||||
chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
@@ -160,7 +155,7 @@ impl Chatlist {
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
} else if flag_archived_only {
|
||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
||||
// show archived chats
|
||||
// (this includes the archived device-chat; we could skip it,
|
||||
// however, then the number of archived chats do not match, which might be even more irritating.
|
||||
@@ -216,7 +211,7 @@ impl Chatlist {
|
||||
)?
|
||||
} else {
|
||||
// show normal chatlist
|
||||
let sort_id_up = if flag_for_forwarding {
|
||||
let sort_id_up = if 0 != listflags & DC_GCL_FOR_FORWARDING {
|
||||
chat::lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
@@ -242,9 +237,9 @@ impl Chatlist {
|
||||
process_row,
|
||||
process_rows,
|
||||
)?;
|
||||
if !flag_no_specials {
|
||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
||||
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) {
|
||||
if !flag_for_forwarding {
|
||||
if 0 == listflags & DC_GCL_FOR_FORWARDING {
|
||||
ids.insert(
|
||||
0,
|
||||
(ChatId::new(DC_CHAT_ID_DEADDROP), last_deaddrop_fresh_msg_id),
|
||||
@@ -257,7 +252,7 @@ impl Chatlist {
|
||||
};
|
||||
|
||||
if add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if ids.is_empty() && flag_add_alldone_hint {
|
||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
||||
ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0)));
|
||||
}
|
||||
ids.push((ChatId::new(DC_CHAT_ID_ARCHIVED_LINK), MsgId::new(0)));
|
||||
|
||||
@@ -65,9 +65,6 @@ pub enum Config {
|
||||
#[strum(props(default = "0"))] // also change ShowEmails.default() on changes
|
||||
ShowEmails,
|
||||
|
||||
#[strum(props(default = "0"))] // also change MediaQuality.default() on changes
|
||||
MediaQuality,
|
||||
|
||||
#[strum(props(default = "0"))]
|
||||
KeyGenType,
|
||||
|
||||
@@ -251,11 +248,9 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
use crate::constants;
|
||||
use crate::constants::AVATAR_SIZE;
|
||||
use crate::test_utils::*;
|
||||
use image::GenericImageView;
|
||||
use num_traits::FromPrimitive;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -351,21 +346,4 @@ mod tests {
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
||||
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_media_quality_config_option() {
|
||||
let t = dummy_context();
|
||||
let media_quality = t.ctx.get_config_int(Config::MediaQuality);
|
||||
assert_eq!(media_quality, 0);
|
||||
let media_quality = constants::MediaQuality::from_i32(media_quality).unwrap_or_default();
|
||||
assert_eq!(media_quality, constants::MediaQuality::Balanced);
|
||||
|
||||
t.ctx.set_config(Config::MediaQuality, Some("1")).unwrap();
|
||||
|
||||
let media_quality = t.ctx.get_config_int(Config::MediaQuality);
|
||||
assert_eq!(media_quality, 1);
|
||||
assert_eq!(constants::MediaQuality::Worse as i32, 1);
|
||||
let media_quality = constants::MediaQuality::from_i32(media_quality).unwrap_or_default();
|
||||
assert_eq!(media_quality, constants::MediaQuality::Worse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status {
|
||||
let mut smtp_connected_here = false;
|
||||
|
||||
let mut param_autoconfig: Option<LoginParam> = None;
|
||||
let was_configured_before = context.is_configured();
|
||||
|
||||
context
|
||||
.inbox_thread
|
||||
@@ -442,15 +441,6 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status {
|
||||
}
|
||||
|
||||
if let Some(provider) = provider::get_provider_info(¶m.addr) {
|
||||
if !was_configured_before {
|
||||
if let Some(config_defaults) = &provider.config_defaults {
|
||||
for def in config_defaults.iter() {
|
||||
info!(context, "apply config_defaults {}={}", def.key, def.value);
|
||||
context.set_config(def.key, Some(def.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !provider.after_login_hint.is_empty() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(provider.after_login_hint.to_string());
|
||||
|
||||
@@ -57,19 +57,6 @@ impl Default for ShowEmails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(u8)]
|
||||
pub enum MediaQuality {
|
||||
Balanced = 0,
|
||||
Worse = 1,
|
||||
}
|
||||
|
||||
impl Default for MediaQuality {
|
||||
fn default() -> Self {
|
||||
MediaQuality::Balanced // also change Config.MediaQuality props(default) on changes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(u8)]
|
||||
pub enum KeyGenType {
|
||||
|
||||
@@ -240,10 +240,7 @@ impl Context {
|
||||
.sql
|
||||
.get_raw_config_int(self, "dbversion")
|
||||
.unwrap_or_default();
|
||||
let journal_mode = self
|
||||
.sql
|
||||
.query_get_value(self, "PRAGMA journal_mode;", rusqlite::NO_PARAMS)
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||
@@ -288,7 +285,6 @@ impl Context {
|
||||
res.insert("number_of_contacts", contacts.to_string());
|
||||
res.insert("database_dir", self.get_dbfile().display().to_string());
|
||||
res.insert("database_version", dbversion.to_string());
|
||||
res.insert("journal_mode", journal_mode);
|
||||
res.insert("blobdir", self.get_blobdir().display().to_string());
|
||||
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
|
||||
res.insert(
|
||||
|
||||
@@ -3,8 +3,6 @@ use sha2::{Digest, Sha256};
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use mailparse::SingleInfo;
|
||||
|
||||
use crate::chat::{self, Chat, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
@@ -34,10 +32,6 @@ enum CreateEvent {
|
||||
}
|
||||
|
||||
/// Receive a message and add it to the database.
|
||||
///
|
||||
/// Returns an error on recoverable errors, e.g. database errors. In this case,
|
||||
/// message parsing should be retried later. If message itself is wrong, logs
|
||||
/// the error and returns success.
|
||||
pub fn dc_receive_imf(
|
||||
context: &Context,
|
||||
imf_raw: &[u8],
|
||||
@@ -61,19 +55,10 @@ pub fn dc_receive_imf(
|
||||
println!("{}", String::from_utf8_lossy(imf_raw));
|
||||
}
|
||||
|
||||
let mut mime_parser = match MimeMessage::from_bytes(context, imf_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "dc_receive_imf: can't parse MIME: {}", err);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(mime_parser) => mime_parser,
|
||||
};
|
||||
let mut mime_parser = MimeMessage::from_bytes(context, imf_raw)?;
|
||||
|
||||
// we can not add even an empty record if we have no info whatsoever
|
||||
if !mime_parser.has_headers() {
|
||||
warn!(context, "dc_receive_imf: no headers found");
|
||||
return Ok(());
|
||||
}
|
||||
ensure!(mime_parser.has_headers(), "No Headers Found");
|
||||
|
||||
// the function returns the number of created messages in the database
|
||||
let mut chat_id = ChatId::new(0);
|
||||
@@ -112,23 +97,29 @@ pub fn dc_receive_imf(
|
||||
// we do not check Return-Path any more as this is unreliable, see
|
||||
// https://github.com/deltachat/deltachat-core/issues/150)
|
||||
let (from_id, from_id_blocked, incoming_origin) =
|
||||
from_field_to_contact_id(context, &mime_parser.from)?;
|
||||
|
||||
if let Some(field_from) = mime_parser.get(HeaderDef::From_) {
|
||||
from_field_to_contact_id(context, field_from)?
|
||||
} else {
|
||||
(0, false, Origin::Unknown)
|
||||
};
|
||||
let incoming = from_id != DC_CONTACT_ID_SELF;
|
||||
|
||||
let mut to_ids = ContactIds::new();
|
||||
|
||||
to_ids.extend(&dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&mime_parser.recipients,
|
||||
if !incoming {
|
||||
Origin::OutgoingTo
|
||||
} else if incoming_origin.is_known() {
|
||||
Origin::IncomingTo
|
||||
} else {
|
||||
Origin::IncomingUnknownTo
|
||||
},
|
||||
)?);
|
||||
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
|
||||
|
||||
@@ -238,11 +229,11 @@ pub fn dc_receive_imf(
|
||||
/// Also returns whether it is blocked or not and its origin.
|
||||
pub fn from_field_to_contact_id(
|
||||
context: &Context,
|
||||
from_address_list: &[SingleInfo],
|
||||
field_from: &str,
|
||||
) -> Result<(u32, bool, Origin)> {
|
||||
let from_ids = dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
from_address_list,
|
||||
&field_from,
|
||||
Origin::IncomingUnknownFrom,
|
||||
)?;
|
||||
|
||||
@@ -252,7 +243,7 @@ pub fn from_field_to_contact_id(
|
||||
if from_ids.len() > 1 {
|
||||
warn!(
|
||||
context,
|
||||
"mail has more than one From address, only using first: {:?}", from_address_list
|
||||
"mail has more than one From address, only using first: {:?}", field_from
|
||||
);
|
||||
}
|
||||
let from_id = from_ids.get_index(0).cloned().unwrap_or_default();
|
||||
@@ -265,10 +256,7 @@ pub fn from_field_to_contact_id(
|
||||
}
|
||||
Ok((from_id, from_id_blocked, incoming_origin))
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"mail has an empty From header: {:?}", from_address_list
|
||||
);
|
||||
warn!(context, "mail has an empty From header: {:?}", field_from);
|
||||
// if there is no from given, from_id stays 0 which is just fine. These messages
|
||||
// are very rare, however, we have to add them to the database (they go to the
|
||||
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
|
||||
@@ -317,8 +305,7 @@ fn add_parts(
|
||||
message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid);
|
||||
}
|
||||
|
||||
warn!(context, "Message already in DB");
|
||||
return Ok(());
|
||||
bail!("Message already in DB");
|
||||
}
|
||||
|
||||
let mut msgrmsg = if mime_parser.has_chat_version() {
|
||||
@@ -382,8 +369,7 @@ fn add_parts(
|
||||
*hidden = true;
|
||||
context.bob.write().unwrap().status = 0; // secure-join failed
|
||||
context.stop_ongoing();
|
||||
warn!(context, "Error in Secure-Join message handling: {}", err);
|
||||
return Ok(());
|
||||
error!(context, "Error in Secure-Join message handling: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,8 +496,7 @@ fn add_parts(
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
warn!(context, "Error in Secure-Join watching: {}", err);
|
||||
return Ok(());
|
||||
error!(context, "Error in Secure-Join watching: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1520,7 +1505,7 @@ fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Ok(ids) = parse_message_ids(mid_list) {
|
||||
if let Ok(ids) = mailparse::msgidparse(mid_list) {
|
||||
for id in ids.iter() {
|
||||
if is_known_rfc724_mid(context, id) {
|
||||
return true;
|
||||
@@ -1568,7 +1553,7 @@ fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -
|
||||
}
|
||||
|
||||
pub(crate) fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
if let Ok(ids) = parse_message_ids(mid_list) {
|
||||
if let Ok(ids) = mailparse::msgidparse(mid_list) {
|
||||
for id in ids.iter() {
|
||||
if is_msgrmsg_rfc724_mid(context, id) {
|
||||
return true;
|
||||
@@ -1592,17 +1577,38 @@ fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
|
||||
fn dc_add_or_lookup_contacts_by_address_list(
|
||||
context: &Context,
|
||||
address_list: &[SingleInfo],
|
||||
addr_list_raw: &str,
|
||||
origin: Origin,
|
||||
) -> Result<ContactIds> {
|
||||
let addrs = match mailparse::addrparse(addr_list_raw) {
|
||||
Ok(addrs) => addrs,
|
||||
Err(err) => {
|
||||
bail!("could not parse {:?}: {:?}", addr_list_raw, err);
|
||||
}
|
||||
};
|
||||
|
||||
let mut contact_ids = ContactIds::new();
|
||||
for info in address_list.iter() {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
origin,
|
||||
)?);
|
||||
for addr in addrs.iter() {
|
||||
match addr {
|
||||
mailparse::MailAddr::Single(info) => {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
origin,
|
||||
)?);
|
||||
}
|
||||
mailparse::MailAddr::Group(infos) => {
|
||||
for info in &infos.addrs {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
origin,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(contact_ids)
|
||||
@@ -1995,151 +2001,4 @@ mod tests {
|
||||
let one2one = Chat::load_from_db(&t.ctx, one2one_id).unwrap();
|
||||
assert!(one2one.get_visibility() == ChatVisibility::Archived);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_from() {
|
||||
// if there is no from given, from_id stays 0 which is just fine. These messages
|
||||
// are very rare, however, we have to add them to the database (they go to the
|
||||
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
|
||||
|
||||
let t = configured_offline_context();
|
||||
let context = &t.ctx;
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
assert!(chats.get_msg_id(0).is_err());
|
||||
|
||||
dc_receive_imf(
|
||||
context,
|
||||
b"To: bob@example.org\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: <3924@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
// Check that the message was added to the database:
|
||||
assert!(chats.get_msg_id(0).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escaped_from() {
|
||||
let t = configured_offline_context();
|
||||
let contact_id = Contact::create(&t.ctx, "foobar", "foobar@example.com").unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&t.ctx, contact_id).unwrap();
|
||||
dc_receive_imf(
|
||||
&t.ctx,
|
||||
b"From: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= <foobar@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: <asdklfjjaweofi@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Disposition-Notification-To: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= <foobar@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
).unwrap();
|
||||
assert_eq!(
|
||||
Contact::load_from_db(&t.ctx, contact_id)
|
||||
.unwrap()
|
||||
.get_authname(),
|
||||
"Фамилия Имя", // The name was "Имя, Фамилия" and ("lastname, firstname") and should be swapped to "firstname, lastname"
|
||||
);
|
||||
let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None);
|
||||
assert_eq!(msgs.len(), 1);
|
||||
let msg_id = msgs.first().unwrap();
|
||||
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()).unwrap();
|
||||
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
|
||||
assert_eq!(msg.text.unwrap(), "hello");
|
||||
assert_eq!(msg.param.get_int(Param::WantsMdn).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escaped_recipients() {
|
||||
let t = configured_offline_context();
|
||||
Contact::create(&t.ctx, "foobar", "foobar@example.com").unwrap();
|
||||
|
||||
let carl_contact_id =
|
||||
Contact::add_or_lookup(&t.ctx, "Carl", "carl@host.tld", Origin::IncomingUnknownFrom)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
dc_receive_imf(
|
||||
&t.ctx,
|
||||
b"From: Foobar <foobar@example.com>\n\
|
||||
To: =?UTF-8?B?0JjQvNGPLCDQpNCw0LzQuNC70LjRjw==?= alice@example.org\n\
|
||||
Cc: =?utf-8?q?=3Ch2=3E?= <carl@host.tld>\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: <asdklfjjaweofi@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Disposition-Notification-To: <foobar@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Contact::load_from_db(&t.ctx, carl_contact_id)
|
||||
.unwrap()
|
||||
.get_name(),
|
||||
"h2"
|
||||
);
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let msg = Message::load_from_db(&t.ctx, chats.get_msg_id(0).unwrap()).unwrap();
|
||||
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
|
||||
assert_eq!(msg.text.unwrap(), "hello");
|
||||
assert_eq!(msg.param.get_int(Param::WantsMdn).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cc_to_contact() {
|
||||
let t = configured_offline_context();
|
||||
Contact::create(&t.ctx, "foobar", "foobar@example.com").unwrap();
|
||||
|
||||
let carl_contact_id = Contact::add_or_lookup(
|
||||
&t.ctx,
|
||||
"garabage",
|
||||
"carl@host.tld",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
dc_receive_imf(
|
||||
&t.ctx,
|
||||
b"From: Foobar <foobar@example.com>\n\
|
||||
To: alice@example.org\n\
|
||||
Cc: Carl <carl@host.tld>\n\
|
||||
Subject: foo\n\
|
||||
Message-ID: <asdklfjjaweofi@example.org>\n\
|
||||
Chat-Version: 1.0\n\
|
||||
Chat-Disposition-Notification-To: <foobar@example.com>\n\
|
||||
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
||||
\n\
|
||||
hello\n",
|
||||
"INBOX",
|
||||
1,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Contact::load_from_db(&t.ctx, carl_contact_id)
|
||||
.unwrap()
|
||||
.get_name(),
|
||||
"Carl"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ use crate::aheader::*;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::error::*;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::headerdef::HeaderDefMap;
|
||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||
use crate::key::{DcKey, Key, SignedPublicKey, SignedSecretKey};
|
||||
use crate::keyring::*;
|
||||
use crate::peerstate::*;
|
||||
@@ -123,8 +122,8 @@ pub fn try_decrypt(
|
||||
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
||||
let from = mail
|
||||
.headers
|
||||
.get_header(HeaderDef::From_)
|
||||
.and_then(|from_addr| mailparse::addrparse_header(&from_addr).ok())
|
||||
.get_header_value(HeaderDef::From_)
|
||||
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
|
||||
.and_then(|from| from.extract_single_info())
|
||||
.map(|from| from.addr)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -53,16 +53,12 @@ impl HeaderDef {
|
||||
|
||||
pub trait HeaderDefMap {
|
||||
fn get_header_value(&self, headerdef: HeaderDef) -> Option<String>;
|
||||
fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader>;
|
||||
}
|
||||
|
||||
impl HeaderDefMap for [MailHeader<'_>] {
|
||||
fn get_header_value(&self, headerdef: HeaderDef) -> Option<String> {
|
||||
self.get_first_value(headerdef.get_headername())
|
||||
}
|
||||
fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader> {
|
||||
self.get_first_header(headerdef.get_headername())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Imap {
|
||||
task::block_on(async move { self.config.read().await.can_idle })
|
||||
}
|
||||
|
||||
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
|
||||
pub fn idle(&self, context: &Context, watch_folder: String) -> Result<()> {
|
||||
task::block_on(async move {
|
||||
if !self.can_idle() {
|
||||
return Err(Error::IdleAbilityMissing);
|
||||
@@ -67,7 +67,7 @@ impl Imap {
|
||||
|
||||
self.setup_handle_if_needed(context).await?;
|
||||
|
||||
self.select_folder(context, watch_folder.clone()).await?;
|
||||
self.select_folder(context, watch_folder).await?;
|
||||
|
||||
let session = self.session.lock().await.take();
|
||||
let timeout = Duration::from_secs(23 * 60);
|
||||
|
||||
@@ -25,7 +25,6 @@ use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::login_param::{CertificateChecks, LoginParam};
|
||||
use crate::message::{self, update_server_uid};
|
||||
use crate::mimeparser;
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::stock::StockMessage;
|
||||
@@ -135,7 +134,7 @@ impl async_imap::Authenticator for OAuth2 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
enum FolderMeaning {
|
||||
Unknown,
|
||||
SentObjects,
|
||||
@@ -269,7 +268,7 @@ impl Imap {
|
||||
let imap_port = config.imap_port;
|
||||
context.stock_string_repl_str2(
|
||||
StockMessage::ServerResponse,
|
||||
format!("IMAP {}:{}", imap_server, imap_port),
|
||||
format!("{}:{}", imap_server, imap_port),
|
||||
err.to_string(),
|
||||
)
|
||||
};
|
||||
@@ -477,7 +476,7 @@ impl Imap {
|
||||
folder: &str,
|
||||
) -> Result<(u32, u32)> {
|
||||
task::block_on(async move {
|
||||
self.select_folder(context, Some(folder)).await?;
|
||||
self.select_folder(context, folder).await?;
|
||||
|
||||
// compare last seen UIDVALIDITY against the current one
|
||||
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder);
|
||||
@@ -605,12 +604,7 @@ impl Imap {
|
||||
|
||||
let headers = get_fetch_headers(fetch)?;
|
||||
let message_id = prefetch_get_message_id(&headers).unwrap_or_default();
|
||||
if let Ok(true) =
|
||||
precheck_imf(context, &message_id, folder.as_ref(), cur_uid).map_err(|err| {
|
||||
warn!(context, "precheck_imf error: {}", err);
|
||||
err
|
||||
})
|
||||
{
|
||||
if precheck_imf(context, &message_id, folder.as_ref(), cur_uid)? {
|
||||
// we know the message-id already or don't want the message otherwise.
|
||||
info!(
|
||||
context,
|
||||
@@ -619,9 +613,6 @@ impl Imap {
|
||||
folder.as_ref(),
|
||||
);
|
||||
} else {
|
||||
// we do not know the message-id
|
||||
// or the message-id is missing (in this case, we create one in the further process)
|
||||
// or some other error happened
|
||||
let show = prefetch_should_download(context, &headers, show_emails)
|
||||
.map_err(|err| {
|
||||
warn!(context, "prefetch_should_download error: {}", err);
|
||||
@@ -742,7 +733,13 @@ impl Imap {
|
||||
if let Err(err) =
|
||||
dc_receive_imf(context, &body, folder.as_ref(), server_uid, is_seen)
|
||||
{
|
||||
return Err(Error::Other(format!("dc_receive_imf error: {}", err)));
|
||||
warn!(
|
||||
context,
|
||||
"dc_receive_imf failed for imap-message {}/{}: {:?}",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -915,7 +912,7 @@ impl Imap {
|
||||
return Some(ImapActionResult::RetryLater);
|
||||
}
|
||||
}
|
||||
match self.select_folder(context, Some(&folder)).await {
|
||||
match self.select_folder(context, &folder).await {
|
||||
Ok(()) => None,
|
||||
Err(select_folder::Error::ConnectionLost) => {
|
||||
warn!(context, "Lost imap connection");
|
||||
@@ -1063,15 +1060,13 @@ impl Imap {
|
||||
}
|
||||
};
|
||||
|
||||
let sentbox_folder = folders
|
||||
.iter()
|
||||
.find(|folder| get_folder_meaning(folder) == FolderMeaning::SentObjects)
|
||||
.or_else(|| {
|
||||
info!(context, "can't find sentbox by attributes, checking names");
|
||||
folders.iter().find(|folder| {
|
||||
get_folder_meaning_by_name(folder) == FolderMeaning::SentObjects
|
||||
})
|
||||
});
|
||||
let sentbox_folder =
|
||||
folders
|
||||
.iter()
|
||||
.find(|folder| match get_folder_meaning(folder) {
|
||||
FolderMeaning::SentObjects => true,
|
||||
_ => false,
|
||||
});
|
||||
info!(context, "sentbox folder is {:?}", sentbox_folder);
|
||||
|
||||
let mut delimiter = ".";
|
||||
@@ -1188,7 +1183,7 @@ impl Imap {
|
||||
error!(context, "could not setup imap connection: {}", err);
|
||||
return;
|
||||
}
|
||||
if let Err(err) = self.select_folder(context, Some(&folder)).await {
|
||||
if let Err(err) = self.select_folder(context, &folder).await {
|
||||
error!(
|
||||
context,
|
||||
"Could not select {} for expunging: {}", folder, err
|
||||
@@ -1206,7 +1201,7 @@ impl Imap {
|
||||
|
||||
// we now trigger expunge to actually delete messages
|
||||
self.config.write().await.selected_folder_needs_expunge = true;
|
||||
match self.select_folder::<String>(context, None).await {
|
||||
match self.select_folder(context, &folder).await {
|
||||
Ok(()) => {
|
||||
emit_event!(context, Event::ImapFolderEmptied(folder.to_string()));
|
||||
}
|
||||
@@ -1237,7 +1232,7 @@ impl Imap {
|
||||
// CAVE: if possible, take care not to add a name here that is "sent" in one language
|
||||
// but sth. different in others - a hard job.
|
||||
fn get_folder_meaning_by_name(folder_name: &Name) -> FolderMeaning {
|
||||
let sent_names = vec!["sent", "sentmail", "sent objects", "gesendet"];
|
||||
let sent_names = vec!["sent", "sent objects", "gesendet"];
|
||||
let lower = folder_name.name().to_lowercase();
|
||||
|
||||
if sent_names.into_iter().any(|s| s == lower) {
|
||||
@@ -1248,18 +1243,27 @@ fn get_folder_meaning_by_name(folder_name: &Name) -> FolderMeaning {
|
||||
}
|
||||
|
||||
fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
|
||||
if folder_name.attributes().is_empty() {
|
||||
return FolderMeaning::Unknown;
|
||||
}
|
||||
|
||||
let mut res = FolderMeaning::Unknown;
|
||||
let special_names = vec!["\\Spam", "\\Trash", "\\Drafts", "\\Junk"];
|
||||
|
||||
for attr in folder_name.attributes() {
|
||||
if let NameAttribute::Custom(ref label) = attr {
|
||||
if special_names.iter().any(|s| *s == label) {
|
||||
return FolderMeaning::Other;
|
||||
res = FolderMeaning::Other;
|
||||
} else if label == "\\Sent" {
|
||||
return FolderMeaning::SentObjects;
|
||||
res = FolderMeaning::SentObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
FolderMeaning::Unknown
|
||||
|
||||
match res {
|
||||
FolderMeaning::Unknown => get_folder_meaning_by_name(folder_name),
|
||||
_ => res,
|
||||
}
|
||||
}
|
||||
|
||||
fn precheck_imf(
|
||||
@@ -1375,8 +1379,11 @@ fn prefetch_should_download(
|
||||
.get_header_value(HeaderDef::AutocryptSetupMessage)
|
||||
.is_some();
|
||||
|
||||
let (_contact_id, blocked_contact, origin) =
|
||||
from_field_to_contact_id(context, &mimeparser::get_from(headers))?;
|
||||
let from_field = headers
|
||||
.get_header_value(HeaderDef::From_)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (_contact_id, blocked_contact, origin) = from_field_to_contact_id(context, &from_field)?;
|
||||
let accepted_contact = origin.is_known();
|
||||
|
||||
let show = is_autocrypt_setup_message
|
||||
|
||||
@@ -56,7 +56,7 @@ impl Imap {
|
||||
pub(super) async fn select_folder<S: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: Option<S>,
|
||||
folder: S,
|
||||
) -> Result<()> {
|
||||
if self.session.lock().await.is_none() {
|
||||
let mut cfg = self.config.write().await;
|
||||
@@ -71,46 +71,41 @@ impl Imap {
|
||||
self.close_folder(context).await?;
|
||||
}
|
||||
|
||||
let folder_str: Option<&str> = folder.as_ref().map(|x| x.as_ref());
|
||||
if self.config.read().await.selected_folder.as_deref() == folder_str {
|
||||
if self.config.read().await.selected_folder.as_deref() == Some(folder.as_ref()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// select new folder
|
||||
if let Some(ref folder) = folder {
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
let res = session.select(folder).await;
|
||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||
let res = session.select(&folder).await;
|
||||
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.3.1
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
// https://tools.ietf.org/html/rfc3501#section-6.3.1
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
let mut config = self.config.write().await;
|
||||
config.selected_folder = Some(folder.as_ref().to_string());
|
||||
config.selected_mailbox = Some(mailbox);
|
||||
Ok(())
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect();
|
||||
self.config.write().await.selected_folder = None;
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.write().await.selected_folder = None;
|
||||
self.trigger_reconnect();
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
let mut config = self.config.write().await;
|
||||
config.selected_folder = Some(folder.as_ref().to_string());
|
||||
config.selected_mailbox = Some(mailbox);
|
||||
Ok(())
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect();
|
||||
self.config.write().await.selected_folder = None;
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.write().await.selected_folder = None;
|
||||
self.trigger_reconnect();
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
} else {
|
||||
Err(Error::NoSession)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
Err(Error::NoSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,7 +1190,7 @@ fn perform_job_action(context: &Context, mut job: &mut Job, thread: Thread, trie
|
||||
Action::ImexImap => match JobImexImap(context, &job) {
|
||||
Ok(()) => Status::Finished(Ok(())),
|
||||
Err(err) => {
|
||||
error!(context, "Import/export failed: {}", err);
|
||||
error!(context, "{}", err);
|
||||
Status::Finished(Err(err))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -173,14 +173,15 @@ impl JobThread {
|
||||
if !self.imap.can_idle() {
|
||||
true // we have to do fake_idle
|
||||
} else {
|
||||
let watch_folder = self.get_watch_folder(context);
|
||||
info!(context, "{} started...", prefix);
|
||||
let res = self.imap.idle(context, watch_folder);
|
||||
info!(context, "{} ended...", prefix);
|
||||
if let Err(err) = res {
|
||||
warn!(context, "{} failed: {} -> reconnecting", prefix, err);
|
||||
// something is borked, let's start afresh on the next occassion
|
||||
self.imap.disconnect(context);
|
||||
if let Some(watch_folder) = self.get_watch_folder(context) {
|
||||
info!(context, "{} started...", prefix);
|
||||
let res = self.imap.idle(context, watch_folder);
|
||||
info!(context, "{} ended...", prefix);
|
||||
if let Err(err) = res {
|
||||
warn!(context, "{} failed: {} -> reconnecting", prefix, err);
|
||||
// something is Label { Label }orked, let's start afresh on the next occassion
|
||||
self.imap.disconnect(context);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1215,7 +1215,7 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef
|
||||
}
|
||||
if let Some(error) = error {
|
||||
msg.param.set(Param::Error, error.as_ref());
|
||||
warn!(context, "Message failed: {}", error.as_ref());
|
||||
error!(context, "{}", error.as_ref());
|
||||
}
|
||||
|
||||
if sql::execute(
|
||||
@@ -1432,10 +1432,7 @@ pub(crate) fn rfc724_mid_exists(
|
||||
context: &Context,
|
||||
rfc724_mid: &str,
|
||||
) -> Result<Option<(String, u32, MsgId)>, Error> {
|
||||
if rfc724_mid.is_empty() {
|
||||
warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
|
||||
return Ok(None);
|
||||
}
|
||||
ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid");
|
||||
|
||||
context
|
||||
.sql
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use lettre_email::mime::{self, Mime};
|
||||
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
|
||||
use mailparse::{DispositionType, MailAddr, MailHeaderMap};
|
||||
|
||||
use crate::aheader::Aheader;
|
||||
use crate::blob::BlobObject;
|
||||
@@ -36,11 +37,6 @@ use crate::stock::StockMessage;
|
||||
pub struct MimeMessage {
|
||||
pub parts: Vec<Part>,
|
||||
header: HashMap<String, String>,
|
||||
|
||||
/// Addresses are normalized and lowercased:
|
||||
pub recipients: Vec<SingleInfo>,
|
||||
pub from: Vec<SingleInfo>,
|
||||
pub chat_disposition_notification_to: Option<SingleInfo>,
|
||||
pub decrypting_failed: bool,
|
||||
pub signatures: HashSet<String>,
|
||||
pub gossipped_addr: HashSet<String>,
|
||||
@@ -92,19 +88,9 @@ impl MimeMessage {
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut headers = Default::default();
|
||||
let mut recipients = Default::default();
|
||||
let mut from = Default::default();
|
||||
let mut chat_disposition_notification_to = None;
|
||||
|
||||
// init known headers with what mailparse provided us
|
||||
MimeMessage::merge_headers(
|
||||
context,
|
||||
&mut headers,
|
||||
&mut recipients,
|
||||
&mut from,
|
||||
&mut chat_disposition_notification_to,
|
||||
&mail.headers,
|
||||
);
|
||||
MimeMessage::merge_headers(&mut headers, &mail.headers);
|
||||
|
||||
// remove headers that are allowed _only_ in the encrypted part
|
||||
headers.remove("secure-join-fingerprint");
|
||||
@@ -132,14 +118,7 @@ impl MimeMessage {
|
||||
|
||||
// let known protected headers from the decrypted
|
||||
// part override the unencrypted top-level
|
||||
MimeMessage::merge_headers(
|
||||
context,
|
||||
&mut headers,
|
||||
&mut recipients,
|
||||
&mut from,
|
||||
&mut chat_disposition_notification_to,
|
||||
&decrypted_mail.headers,
|
||||
);
|
||||
MimeMessage::merge_headers(&mut headers, &decrypted_mail.headers);
|
||||
|
||||
(decrypted_mail, signatures)
|
||||
} else {
|
||||
@@ -163,9 +142,6 @@ impl MimeMessage {
|
||||
let mut parser = MimeMessage {
|
||||
parts: Vec::new(),
|
||||
header: headers,
|
||||
recipients,
|
||||
from,
|
||||
chat_disposition_notification_to,
|
||||
decrypting_failed: false,
|
||||
|
||||
// only non-empty if it was a valid autocrypt message
|
||||
@@ -334,9 +310,11 @@ impl MimeMessage {
|
||||
|
||||
// See if an MDN is requested from the other side
|
||||
if !self.decrypting_failed && !self.parts.is_empty() {
|
||||
if let Some(ref dn_to) = self.chat_disposition_notification_to {
|
||||
if let Some(ref from) = self.from.get(0) {
|
||||
if from.addr == dn_to.addr {
|
||||
if let Some(ref dn_to_addr) =
|
||||
self.parse_first_addr(context, HeaderDef::ChatDispositionNotificationTo)
|
||||
{
|
||||
if let Some(ref from_addr) = self.parse_first_addr(context, HeaderDef::From_) {
|
||||
if compare_addrs(from_addr, dn_to_addr) {
|
||||
if let Some(part) = self.parts.last_mut() {
|
||||
part.param.set_int(Param::WantsMdn, 1);
|
||||
}
|
||||
@@ -410,6 +388,20 @@ impl MimeMessage {
|
||||
self.header.get(headerdef.get_headername())
|
||||
}
|
||||
|
||||
fn parse_first_addr(&self, context: &Context, headerdef: HeaderDef) -> Option<MailAddr> {
|
||||
if let Some(value) = self.get(headerdef.clone()) {
|
||||
match mailparse::addrparse(&value) {
|
||||
Ok(ref addrs) => {
|
||||
return addrs.first().cloned();
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "header {} parse error: {:?}", headerdef, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_mime_recursive(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
@@ -753,41 +745,17 @@ impl MimeMessage {
|
||||
.and_then(|msgid| parse_message_id(msgid).ok())
|
||||
}
|
||||
|
||||
fn merge_headers(
|
||||
context: &Context,
|
||||
headers: &mut HashMap<String, String>,
|
||||
recipients: &mut Vec<SingleInfo>,
|
||||
from: &mut Vec<SingleInfo>,
|
||||
chat_disposition_notification_to: &mut Option<SingleInfo>,
|
||||
fields: &[mailparse::MailHeader<'_>],
|
||||
) {
|
||||
fn merge_headers(headers: &mut HashMap<String, String>, fields: &[mailparse::MailHeader<'_>]) {
|
||||
for field in fields {
|
||||
// lowercasing all headers is technically not correct, but makes things work better
|
||||
let key = field.get_key().to_lowercase();
|
||||
if !headers.contains_key(&key) || // key already exists, only overwrite known types (protected headers)
|
||||
is_known(&key) || key.starts_with("chat-")
|
||||
{
|
||||
if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
|
||||
match addrparse_header(field) {
|
||||
Ok(addrlist) => {
|
||||
*chat_disposition_notification_to = addrlist.extract_single_info();
|
||||
}
|
||||
Err(e) => warn!(context, "Could not read {} address: {}", key, e),
|
||||
}
|
||||
} else {
|
||||
let value = field.get_value();
|
||||
headers.insert(key.to_string(), value);
|
||||
}
|
||||
let value = field.get_value();
|
||||
headers.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
let recipients_new = get_recipients(fields);
|
||||
if !recipients_new.is_empty() {
|
||||
*recipients = recipients_new;
|
||||
}
|
||||
let from_new = get_from(fields);
|
||||
if !from_new.is_empty() {
|
||||
*from = from_new;
|
||||
}
|
||||
}
|
||||
|
||||
fn process_report(
|
||||
@@ -855,15 +823,23 @@ fn update_gossip_peerstates(
|
||||
gossip_headers: Vec<String>,
|
||||
) -> Result<HashSet<String>> {
|
||||
// XXX split the parsing from the modification part
|
||||
let mut recipients: Option<HashSet<String>> = None;
|
||||
let mut gossipped_addr: HashSet<String> = Default::default();
|
||||
|
||||
for value in &gossip_headers {
|
||||
let gossip_header = value.parse::<Aheader>();
|
||||
|
||||
if let Ok(ref header) = gossip_header {
|
||||
if get_recipients(&mail.headers)
|
||||
.iter()
|
||||
.any(|info| info.addr == header.addr.to_lowercase())
|
||||
if recipients.is_none() {
|
||||
recipients = Some(get_recipients(
|
||||
mail.headers.iter().map(|v| (v.get_key(), v.get_value())),
|
||||
));
|
||||
}
|
||||
|
||||
if recipients
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&header.addr.to_lowercase())
|
||||
{
|
||||
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
@@ -901,29 +877,13 @@ pub(crate) struct Report {
|
||||
additional_message_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_message_ids(ids: &str) -> Result<Vec<String>> {
|
||||
// take care with mailparse::msgidparse() that is pretty untolerant eg. wrt missing `<` or `>`
|
||||
let mut msgids = Vec::new();
|
||||
for id in ids.split_whitespace() {
|
||||
let mut id = id.to_string();
|
||||
if id.starts_with('<') {
|
||||
id = id[1..].to_string();
|
||||
}
|
||||
if id.ends_with('>') {
|
||||
id = id[..id.len() - 1].to_string();
|
||||
}
|
||||
if !id.is_empty() {
|
||||
msgids.push(id);
|
||||
}
|
||||
}
|
||||
Ok(msgids)
|
||||
}
|
||||
pub(crate) fn parse_message_id(value: &str) -> crate::error::Result<String> {
|
||||
let ids = mailparse::msgidparse(value).context("failed to parse message id")?;
|
||||
|
||||
pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
|
||||
if let Some(id) = parse_message_ids(ids)?.first() {
|
||||
if let Some(id) = ids.first() {
|
||||
Ok(id.to_string())
|
||||
} else {
|
||||
bail!("could not parse message_id: {}", ids);
|
||||
bail!("could not parse message_id: {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,11 +984,6 @@ fn get_attachment_filename(mail: &mailparse::ParsedMail) -> Result<Option<String
|
||||
let desired_filename =
|
||||
desired_filename.or_else(|| ct.params.get("name").map(|s| s.to_string()));
|
||||
|
||||
// MS Outlook is known to specify filename in the "name" attribute of
|
||||
// Content-Type and omit Content-Disposition.
|
||||
let desired_filename =
|
||||
desired_filename.or_else(|| mail.ctype.params.get("name").map(|s| s.to_string()));
|
||||
|
||||
// If there is no filename, but part is an attachment, guess filename
|
||||
if ct.disposition == DispositionType::Attachment && desired_filename.is_none() {
|
||||
if let Some(subtype) = mail.ctype.mimetype.split('/').nth(1) {
|
||||
@@ -1044,50 +999,50 @@ fn get_attachment_filename(mail: &mailparse::ParsedMail) -> Result<Option<String
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned addresses are normalized and lowercased.
|
||||
fn get_recipients(headers: &[MailHeader]) -> Vec<SingleInfo> {
|
||||
get_all_addresses_from_header(headers, |header_key| {
|
||||
header_key == "to" || header_key == "cc"
|
||||
})
|
||||
}
|
||||
// returned addresses are normalized and lowercased.
|
||||
fn get_recipients<S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> HashSet<String> {
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
/// Returned addresses are normalized and lowercased.
|
||||
pub(crate) fn get_from(headers: &[MailHeader]) -> Vec<SingleInfo> {
|
||||
get_all_addresses_from_header(headers, |header_key| header_key == "from")
|
||||
}
|
||||
|
||||
fn get_all_addresses_from_header<F>(headers: &[MailHeader], pred: F) -> Vec<SingleInfo>
|
||||
where
|
||||
F: Fn(String) -> bool,
|
||||
{
|
||||
let mut result: Vec<SingleInfo> = Default::default();
|
||||
|
||||
headers
|
||||
.iter()
|
||||
.filter(|header| pred(header.get_key().to_lowercase()))
|
||||
.filter_map(|header| mailparse::addrparse_header(header).ok())
|
||||
.for_each(|addrs| {
|
||||
for addr in addrs.iter() {
|
||||
match addr {
|
||||
mailparse::MailAddr::Single(ref info) => {
|
||||
result.push(SingleInfo {
|
||||
addr: addr_normalize(&info.addr).to_lowercase(),
|
||||
display_name: info.display_name.clone(),
|
||||
});
|
||||
}
|
||||
mailparse::MailAddr::Group(ref infos) => {
|
||||
for info in &infos.addrs {
|
||||
result.push(SingleInfo {
|
||||
addr: addr_normalize(&info.addr).to_lowercase(),
|
||||
display_name: info.display_name.clone(),
|
||||
});
|
||||
for (hkey, hvalue) in headers {
|
||||
let hkey = hkey.as_ref().to_lowercase();
|
||||
let hvalue = hvalue.as_ref();
|
||||
if hkey == "to" || hkey == "cc" {
|
||||
if let Ok(addrs) = mailparse::addrparse(hvalue) {
|
||||
for addr in addrs.iter() {
|
||||
match addr {
|
||||
mailparse::MailAddr::Single(ref info) => {
|
||||
recipients.insert(addr_normalize(&info.addr).to_lowercase());
|
||||
}
|
||||
mailparse::MailAddr::Group(ref infos) => {
|
||||
for info in &infos.addrs {
|
||||
recipients.insert(addr_normalize(&info.addr).to_lowercase());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
recipients
|
||||
}
|
||||
|
||||
/// Check if the only addrs match, ignoring names.
|
||||
fn compare_addrs(a: &mailparse::MailAddr, b: &mailparse::MailAddr) -> bool {
|
||||
match a {
|
||||
mailparse::MailAddr::Group(group_a) => match b {
|
||||
mailparse::MailAddr::Group(group_b) => group_a
|
||||
.addrs
|
||||
.iter()
|
||||
.zip(group_b.addrs.iter())
|
||||
.all(|(a, b)| a.addr == b.addr),
|
||||
_ => false,
|
||||
},
|
||||
mailparse::MailAddr::Single(single_a) => match b {
|
||||
mailparse::MailAddr::Single(single_b) => single_a.addr == single_b.addr,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1136,11 +1091,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_recipients() {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/mail_with_cc.txt");
|
||||
let mail = mailparse::parse_mail(&raw[..]).unwrap();
|
||||
let recipients = get_recipients(&mail.headers);
|
||||
assert!(recipients.iter().any(|info| info.addr == "abc@bcd.com"));
|
||||
assert!(recipients.iter().any(|info| info.addr == "def@def.de"));
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let recipients = get_recipients(mimeparser.header.iter());
|
||||
assert!(recipients.contains("abc@bcd.com"));
|
||||
assert!(recipients.contains("def@def.de"));
|
||||
assert_eq!(recipients.len(), 2);
|
||||
}
|
||||
|
||||
@@ -1195,10 +1151,14 @@ mod tests {
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
let of = &mimeparser.from[0];
|
||||
assert_eq!(of.addr, "hello@one.org");
|
||||
let of = mimeparser
|
||||
.parse_first_addr(&context.ctx, HeaderDef::From_)
|
||||
.unwrap();
|
||||
assert_eq!(of, mailparse::addrparse("hello@one.org").unwrap()[0]);
|
||||
|
||||
assert!(mimeparser.chat_disposition_notification_to.is_none());
|
||||
let of =
|
||||
mimeparser.parse_first_addr(&context.ctx, HeaderDef::ChatDispositionNotificationTo);
|
||||
assert!(of.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1656,142 +1616,4 @@ CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I=
|
||||
assert_eq!(message.parts[0].typ, Viewtype::Image);
|
||||
assert_eq!(message.parts[0].msg, "Test");
|
||||
}
|
||||
|
||||
// Outlook specifies filename in the "name" attribute of Content-Type
|
||||
#[test]
|
||||
fn parse_outlook_html_embedded_image() {
|
||||
let context = dummy_context();
|
||||
let raw = br##"From: Anonymous <anonymous@example.org>
|
||||
To: Anonymous <anonymous@example.org>
|
||||
Subject: Delta Chat is great stuff!
|
||||
Date: Tue, 5 May 2020 01:23:45 +0000
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/related;
|
||||
boundary="----=_NextPart_000_0003_01D622B3.CA753E60"
|
||||
X-Mailer: Microsoft Outlook 15.0
|
||||
|
||||
This is a multipart message in MIME format.
|
||||
|
||||
------=_NextPart_000_0003_01D622B3.CA753E60
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_NextPart_001_0004_01D622B3.CA753E60"
|
||||
|
||||
|
||||
------=_NextPart_001_0004_01D622B3.CA753E60
|
||||
Content-Type: text/plain;
|
||||
charset="us-ascii"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
|
||||
|
||||
|
||||
------=_NextPart_001_0004_01D622B3.CA753E60
|
||||
Content-Type: text/html;
|
||||
charset="us-ascii"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Test<img src="cid:image001.jpg@01D622B3.C9D8D750">
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
------=_NextPart_001_0004_01D622B3.CA753E60--
|
||||
|
||||
------=_NextPart_000_0003_01D622B3.CA753E60
|
||||
Content-Type: image/jpeg;
|
||||
name="image001.jpg"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-ID: <image001.jpg@01D622B3.C9D8D750>
|
||||
|
||||
ISVb1L3m7z15Wy5w97a2cJg6W8P8YKOYfWn3PJ/UCSFcvCPtvBhcXieiN3M3ljguzG4XK7BnGgxG
|
||||
acAQdY8e0cWz1n+zKPNeNn4Iu3GXAXz4/IPksHk54inl1//0Lv8ggZjljfjnf0q1SPftYI7lpZWT
|
||||
/4aTCkimRrAIcwrQJPnZJRb7BPSC6kfn1QJHMv77mRMz2+4WbdfpyPQQ0CWLJsgVXtBsSMf2Awal
|
||||
n+zZzhGpXyCbWTEw1ccqZcK5KaiKNqWv51N4yVXw9dzJoCvxbYtCFGZZJdx7c+ObDotaF1/9KY4C
|
||||
xJjgK9/NgTXCZP1jYm0XIBnJsFSNg0pnMRETttTuGbOVi1/s/F1RGv5RNZsCUt21d9FhkWQQXsd2
|
||||
rOzDgTdag6BQCN3hSU9eKW/GhNBuMibRN9eS7Sm1y2qFU1HgGJBQfPPRPLKxXaNi++Zt0tnon2IU
|
||||
8pg5rP/IvStXYQNUQ9SiFdfAUkLU5b1j8ltnka8xl+oXsleSG44GPz6kM0RmwUrGkl4z/+NfHSsI
|
||||
K+TuvC7qOah0WLFhcsXWn2+dDV1bXuAeC769TkqkpHhdXfUHnVgK3Pv7u3rVPT5AMeFUGxRB2dP4
|
||||
CWt6wx7fiLp0qS9RrX75g6Gqw7nfCs6EcBERcIPt7DTe8VStJwf3LWqVwxl4gQl46yhfoqwEO+I=
|
||||
|
||||
------=_NextPart_000_0003_01D622B3.CA753E60--
|
||||
"##;
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(
|
||||
message.get_subject(),
|
||||
Some("Delta Chat is great stuff!".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(message.parts.len(), 1);
|
||||
assert_eq!(message.parts[0].typ, Viewtype::Image);
|
||||
assert_eq!(message.parts[0].msg, "Test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_message_id() {
|
||||
let test = parse_message_id("<foobar>");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foobar");
|
||||
|
||||
let test = parse_message_id("<foo> <bar>");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foo");
|
||||
|
||||
let test = parse_message_id(" < foo > <bar>");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foo");
|
||||
|
||||
let test = parse_message_id("foo");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foo");
|
||||
|
||||
let test = parse_message_id(" foo ");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foo");
|
||||
|
||||
let test = parse_message_id("foo bar");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foo");
|
||||
|
||||
let test = parse_message_id(" foo bar ");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "foo");
|
||||
|
||||
let test = parse_message_id("");
|
||||
assert!(test.is_err());
|
||||
|
||||
let test = parse_message_id(" ");
|
||||
assert!(test.is_err());
|
||||
|
||||
let test = parse_message_id("<>");
|
||||
assert!(test.is_err());
|
||||
|
||||
let test = parse_message_id("<> bar");
|
||||
assert!(test.is_ok());
|
||||
assert_eq!(test.unwrap(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_message_ids() {
|
||||
let test = parse_message_ids(" foo bar <foobar>").unwrap();
|
||||
assert_eq!(test.len(), 3);
|
||||
assert_eq!(test[0], "foo");
|
||||
assert_eq!(test[1], "bar");
|
||||
assert_eq!(test[2], "foobar");
|
||||
|
||||
let test = parse_message_ids(" < foobar >").unwrap();
|
||||
assert_eq!(test.len(), 1);
|
||||
assert_eq!(test[0], "foobar");
|
||||
|
||||
let test = parse_message_ids("").unwrap();
|
||||
assert!(test.is_empty());
|
||||
|
||||
let test = parse_message_ids(" ").unwrap();
|
||||
assert!(test.is_empty());
|
||||
|
||||
let test = parse_message_ids(" < ").unwrap();
|
||||
assert!(test.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "newyear.aktivix.org", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "newyear.aktivix.org", port: 25, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// aol.md: aol.com
|
||||
@@ -29,7 +28,6 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/aol",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// autistici.org.md: autistici.org
|
||||
@@ -42,7 +40,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "mail.autistici.org", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.autistici.org", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// bluewin.ch.md: bluewin.ch
|
||||
@@ -55,7 +52,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imaps.bluewin.ch", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtpauths.bluewin.ch", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// comcast.md: xfinity.com, comcast.net
|
||||
@@ -77,7 +73,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.example.com", port: 1337, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.example.com", port: 1337, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// fastmail.md: fastmail.com
|
||||
@@ -88,7 +83,6 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/fastmail",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// freenet.de.md: freenet.de
|
||||
@@ -101,7 +95,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "mx.freenet.de", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "mx.freenet.de", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// gmail.md: gmail.com, googlemail.com
|
||||
@@ -114,7 +107,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.gmail.com", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.gmail.com", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// gmx.net.md: gmx.net, gmx.de, gmx.at, gmx.ch, gmx.org, gmx.eu, gmx.info, gmx.biz, gmx.com
|
||||
@@ -128,7 +120,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "mail.gmx.net", port: 465, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "mail.gmx.net", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// i.ua.md: i.ua
|
||||
@@ -144,7 +135,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.mail.me.com", port: 993, username_pattern: EMAILLOCALPART },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.mail.me.com", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// kolst.com.md: kolst.com
|
||||
@@ -169,15 +159,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "imap.nauta.cu", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.nauta.cu", port: 25, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: Some(vec![
|
||||
ConfigDefault { key: Config::DeleteServerAfter, value: "1" },
|
||||
ConfigDefault { key: Config::BccSelf, value: "0" },
|
||||
ConfigDefault { key: Config::SentboxWatch, value: "0" },
|
||||
ConfigDefault { key: Config::MvboxWatch, value: "0" },
|
||||
ConfigDefault { key: Config::MvboxMove, value: "0" },
|
||||
ConfigDefault { key: Config::E2eeEnabled, value: "0" },
|
||||
ConfigDefault { key: Config::MediaQuality, value: "1" },
|
||||
]),
|
||||
};
|
||||
|
||||
// outlook.com.md: hotmail.com, outlook.com, office365.com, outlook.com.tr, live.com
|
||||
@@ -190,7 +171,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap-mail.outlook.com", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp-mail.outlook.com", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// posteo.md: posteo.de
|
||||
@@ -203,7 +183,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "posteo.de", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "posteo.de", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// protonmail.md: protonmail.com, protonmail.ch
|
||||
@@ -214,7 +193,6 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/protonmail",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// riseup.net.md: riseup.net
|
||||
@@ -223,9 +201,6 @@ lazy_static::lazy_static! {
|
||||
// rogers.com.md: rogers.com
|
||||
// - skipping provider with status OK and no special things to do
|
||||
|
||||
// systemli.org.md: systemli.org
|
||||
// - skipping provider with status OK and no special things to do
|
||||
|
||||
// t-online.md: t-online.de, magenta.de
|
||||
static ref P_T_ONLINE: Provider = Provider {
|
||||
status: Status::PREPARATION,
|
||||
@@ -234,7 +209,6 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/t-online",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// testrun.md: testrun.org
|
||||
@@ -248,7 +222,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "testrun.org", port: 143, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "testrun.org", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// tiscali.it.md: tiscali.it
|
||||
@@ -261,7 +234,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.tiscali.it", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.tiscali.it", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// ukr.net.md: ukr.net
|
||||
@@ -281,7 +253,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: STARTTLS, hostname: "imap.web.de", port: 143, username_pattern: EMAILLOCALPART },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.web.de", port: 587, username_pattern: EMAILLOCALPART },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// yahoo.md: yahoo.com, yahoo.de, yahoo.it, yahoo.fr, yahoo.es, yahoo.se, yahoo.co.uk, yahoo.co.nz, yahoo.com.au, yahoo.com.ar, yahoo.com.br, yahoo.com.mx, ymail.com, rocketmail.com, yahoodns.net
|
||||
@@ -294,7 +265,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.mail.yahoo.com", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: SSL, hostname: "smtp.mail.yahoo.com", port: 465, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// yandex.ru.md: yandex.ru, yandex.com
|
||||
@@ -305,7 +275,6 @@ lazy_static::lazy_static! {
|
||||
overview_page: "https://providers.delta.chat/yandex-ru",
|
||||
server: vec![
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
// ziggo.nl.md: ziggo.nl
|
||||
@@ -318,7 +287,6 @@ lazy_static::lazy_static! {
|
||||
Server { protocol: IMAP, socket: SSL, hostname: "imap.ziggo.nl", port: 993, username_pattern: EMAIL },
|
||||
Server { protocol: SMTP, socket: STARTTLS, hostname: "smtp.ziggo.nl", port: 587, username_pattern: EMAIL },
|
||||
],
|
||||
config_defaults: None,
|
||||
};
|
||||
|
||||
pub static ref PROVIDER_DATA: HashMap<&'static str, &'static Provider> = [
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
mod data;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::dc_tools::EmailAddress;
|
||||
use crate::provider::data::PROVIDER_DATA;
|
||||
|
||||
@@ -58,12 +57,6 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigDefault {
|
||||
pub key: Config,
|
||||
pub value: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Provider {
|
||||
pub status: Status,
|
||||
@@ -71,7 +64,6 @@ pub struct Provider {
|
||||
pub after_login_hint: &'static str,
|
||||
pub overview_page: &'static str,
|
||||
pub server: Vec<Server>,
|
||||
pub config_defaults: Option<Vec<ConfigDefault>>,
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
|
||||
@@ -9,9 +9,6 @@ out_all = ""
|
||||
out_domains = ""
|
||||
domains_dict = {}
|
||||
|
||||
def camel(name):
|
||||
words = name.split("_")
|
||||
return "".join(w.capitalize() for i, w in enumerate(words))
|
||||
|
||||
def cleanstr(s):
|
||||
s = s.strip()
|
||||
@@ -34,18 +31,6 @@ def file2url(f):
|
||||
return "https://providers.delta.chat/" + f
|
||||
|
||||
|
||||
def process_config_defaults(data):
|
||||
if not "config_defaults" in data:
|
||||
return "None"
|
||||
defaults = "Some(vec![\n"
|
||||
config_defaults = data.get("config_defaults", "")
|
||||
for key in config_defaults:
|
||||
value = str(config_defaults[key])
|
||||
defaults += " ConfigDefault { key: Config::" + camel(key) + ", value: \"" + value + "\" },\n"
|
||||
defaults += " ])"
|
||||
return defaults
|
||||
|
||||
|
||||
def process_data(data, file):
|
||||
status = data.get("status", "")
|
||||
if status != "OK" and status != "PREPARATION" and status != "BROKEN":
|
||||
@@ -98,8 +83,6 @@ def process_data(data, file):
|
||||
server += (" Server { protocol: " + protocol + ", socket: " + socket + ", hostname: \""
|
||||
+ hostname + "\", port: " + str(port) + ", username_pattern: " + username_pattern + " },\n")
|
||||
|
||||
config_defaults = process_config_defaults(data)
|
||||
|
||||
provider = ""
|
||||
before_login_hint = cleanstr(data.get("before_login_hint", ""))
|
||||
after_login_hint = cleanstr(data.get("after_login_hint", ""))
|
||||
@@ -110,7 +93,6 @@ def process_data(data, file):
|
||||
provider += " after_login_hint: \"" + after_login_hint + "\",\n"
|
||||
provider += " overview_page: \"" + file2url(file) + "\",\n"
|
||||
provider += " server: vec![\n" + server + " ],\n"
|
||||
provider += " config_defaults: " + config_defaults + ",\n"
|
||||
provider += " };\n\n"
|
||||
else:
|
||||
raise TypeError("SMTP and IMAP must be specified together or left out both")
|
||||
@@ -121,7 +103,7 @@ def process_data(data, file):
|
||||
# finally, add the provider
|
||||
global out_all, out_domains
|
||||
out_all += " // " + file[file.rindex("/")+1:] + ": " + comment.strip(", ") + "\n"
|
||||
if status == "OK" and before_login_hint == "" and after_login_hint == "" and server == "" and config_defaults == "None":
|
||||
if status == "OK" and before_login_hint == "" and after_login_hint == "" and server == "":
|
||||
out_all += " // - skipping provider with status OK and no special things to do\n\n"
|
||||
else:
|
||||
out_all += provider
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::context::Context;
|
||||
use crate::events::Event;
|
||||
use crate::login_param::{dc_build_tls, LoginParam};
|
||||
use crate::oauth2::*;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// SMTP write and read timeout in seconds.
|
||||
const SMTP_TIMEOUT: u64 = 30;
|
||||
@@ -172,14 +171,7 @@ impl Smtp {
|
||||
let mut trans = client.into_transport();
|
||||
|
||||
trans.connect().await.map_err(|err| {
|
||||
let message = {
|
||||
context.stock_string_repl_str2(
|
||||
StockMessage::ServerResponse,
|
||||
format!("SMTP {}:{}", domain, port),
|
||||
err.to_string(),
|
||||
)
|
||||
};
|
||||
emit_event!(context, Event::ErrorNetwork(message));
|
||||
emit_event!(context, Event::ErrorNetwork(err.to_string()));
|
||||
Error::ConnectionFailure(err)
|
||||
})?;
|
||||
|
||||
|
||||
17
src/sql.rs
17
src/sql.rs
@@ -242,7 +242,7 @@ impl Sql {
|
||||
match self.query_get_value_result(query, params) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
warn!(context, "sql: Failed query_row: {}", err);
|
||||
error!(context, "sql: Failed query_row: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -398,10 +398,6 @@ fn open(
|
||||
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
|
||||
open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE);
|
||||
}
|
||||
|
||||
// this actually creates min_idle database handles just now.
|
||||
// therefore, with_init() must not try to modify the database as otherwise
|
||||
// we easily get busy-errors (eg. table-creation, journal_mode etc. should be done on only one handle)
|
||||
let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref())
|
||||
.with_flags(open_flags)
|
||||
.with_init(|c| c.execute_batch("PRAGMA secure_delete=on;"));
|
||||
@@ -417,17 +413,6 @@ fn open(
|
||||
}
|
||||
|
||||
if !readonly {
|
||||
// journal_mode is persisted, it is sufficient to change it only for one handle.
|
||||
// (nb: execute() always returns errors for this PRAGMA call, just discard it.
|
||||
// but even if execute() would handle errors more gracefully, we should continue on errors -
|
||||
// systems might not be able to handle WAL, in which case the standard-journal is used.
|
||||
// that may be not optimal, but better than not working at all :)
|
||||
|
||||
// some tests are failing reproducible with journal_mode=WAL but pass with journal_mode=DELETE -
|
||||
// until we have more details on that or the tests pass again, we disable WAL.
|
||||
// as no core was shipped with WAL, there is no need to disable it explicitly.
|
||||
//sql.execute("PRAGMA journal_mode=WAL;", NO_PARAMS).ok();
|
||||
|
||||
let mut exists_before_update = false;
|
||||
let mut dbversion_before_update = 0;
|
||||
/* Init tables to dbversion=0 */
|
||||
|
||||
Reference in New Issue
Block a user