mirror of
https://github.com/chatmail/core.git
synced 2026-06-26 01:26:36 +03:00
Compare commits
1 Commits
fix1079
...
remove_err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21f447f23c |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,32 +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
|
||||
message
|
||||
|
||||
- #1055 avoid panicking when we don't have a selected folder
|
||||
|
||||
- #1052 #1049 #1051 improve logging to add thread-id/name and
|
||||
file/lineno to each info/warn message.
|
||||
|
||||
- #1050 allow python bindings to initialize Account with "os_name".
|
||||
|
||||
|
||||
## 1.0.0-beta.17
|
||||
|
||||
- #1044 implement avatar recoding to 192x192 in core to keep file sizes small.
|
||||
|
||||
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -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?rev=d7836416766b55d8d03587ea5326eecf501c2030#d7836416766b55d8d03587ea5326eecf501c2030"
|
||||
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,9 +657,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.20"
|
||||
version = "1.0.0-beta.17"
|
||||
dependencies = [
|
||||
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)",
|
||||
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap?rev=d7836416766b55d8d03587ea5326eecf501c2030)",
|
||||
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-smtp 0.1.0 (git+https://github.com/async-email/async-smtp)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -735,15 +734,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.20"
|
||||
version = "1.0.0-beta.17"
|
||||
dependencies = [
|
||||
"deltachat 1.0.0-beta.20",
|
||||
"deltachat 1.0.0-beta.17",
|
||||
"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]]
|
||||
@@ -3471,7 +3469,7 @@ dependencies = [
|
||||
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
|
||||
"checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
|
||||
"checksum async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)" = "<none>"
|
||||
"checksum async-imap 0.1.1 (git+https://github.com/async-email/async-imap?rev=d7836416766b55d8d03587ea5326eecf501c2030)" = "<none>"
|
||||
"checksum async-macros 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "644a5a8de80f2085a1e7e57cd1544a2a7438f6e003c0790999bd43b92a77cdb2"
|
||||
"checksum async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a29e9e4ed87f4070dd6099d0bd5edfc7692c94442c80d656b50a802c6fde23b7"
|
||||
"checksum async-smtp 0.1.0 (git+https://github.com/async-email/async-smtp)" = "<none>"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.20"
|
||||
version = "1.0.0-beta.17"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
@@ -17,12 +17,12 @@ 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" }
|
||||
|
||||
# XXX newer commits of async-imap lead to import-export tests hanging
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", branch = "dcc-stable" }
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", rev="d7836416766b55d8d03587ea5326eecf501c2030"}
|
||||
|
||||
async-native-tls = "0.1.1"
|
||||
async-std = { version = "1.0", features = ["unstable"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.20"
|
||||
version = "1.0.0-beta.17"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
@@ -21,7 +21,6 @@ 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"]
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#[macro_use]
|
||||
extern crate human_panic;
|
||||
extern crate num_traits;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
@@ -510,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(())
|
||||
}
|
||||
|
||||
@@ -2391,27 +2390,12 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
let chat = match chat::Chat::load_from_db(ctx, chat_id) {
|
||||
Ok(chat) => chat,
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
let info = match chat.get_info(ctx) {
|
||||
Ok(info) => info,
|
||||
Err(err) => {
|
||||
error!(
|
||||
ctx,
|
||||
"dc_get_chat_info_json() failed to get chat info: {}", err
|
||||
);
|
||||
return "".strdup();
|
||||
}
|
||||
};
|
||||
serde_json::to_string(&info)
|
||||
.unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json")
|
||||
.strdup()
|
||||
.with_inner(|ctx| match chat::get_info_json(ctx, chat_id) {
|
||||
Ok(s) => s.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "get_info_json({}) returned: {}", chat_id, err);
|
||||
return "".strdup();
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| "".strdup())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -26,7 +26,7 @@ class Account(object):
|
||||
by the underlying deltachat core library. All public Account methods are
|
||||
meant to be memory-safe and return memory-safe objects.
|
||||
"""
|
||||
def __init__(self, db_path, logid=None, eventlogging=True, os_name=None, debug=True):
|
||||
def __init__(self, db_path, logid=None, eventlogging=True, debug=True):
|
||||
""" initialize account object.
|
||||
|
||||
:param db_path: a path to the account database. The database
|
||||
@@ -34,11 +34,10 @@ class Account(object):
|
||||
:param logid: an optional logging prefix that should be used with
|
||||
the default internal logging.
|
||||
:param eventlogging: if False no eventlogging and no context callback will be configured
|
||||
:param os_name: this will be put to the X-Mailer header in outgoing messages
|
||||
:param debug: turn on debug logging for events.
|
||||
"""
|
||||
self._dc_context = ffi.gc(
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, as_dc_charpointer(os_name)),
|
||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
_destroy_dc_context,
|
||||
)
|
||||
if eventlogging:
|
||||
|
||||
@@ -16,14 +16,6 @@ class TestOfflineAccountBasic:
|
||||
with pytest.raises(ValueError):
|
||||
Account(p.strpath)
|
||||
|
||||
def test_os_name(self, tmpdir):
|
||||
p = tmpdir.join("hello.db")
|
||||
# we can't easily test if os_name is used in X-Mailer
|
||||
# outgoing messages without a full Online test
|
||||
# but we at least check Account accepts the arg
|
||||
ac1 = Account(p.strpath, os_name="solarpunk")
|
||||
ac1.get_info()
|
||||
|
||||
def test_getinfo(self, acfactory):
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
d = ac1.get_info()
|
||||
@@ -1209,15 +1201,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)
|
||||
|
||||
169
src/chat.rs
169
src/chat.rs
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use itertools::Itertools;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::blob::{BlobError, BlobObject};
|
||||
use crate::chatlist::*;
|
||||
@@ -240,30 +240,6 @@ impl Chat {
|
||||
color
|
||||
}
|
||||
|
||||
/// Returns a struct describing the current state of the chat.
|
||||
///
|
||||
/// This is somewhat experimental, even more so than the rest of
|
||||
/// deltachat, and the data returned is still subject to change.
|
||||
pub fn get_info(&self, context: &Context) -> Result<ChatInfo, Error> {
|
||||
let draft = match get_draft(context, self.id)? {
|
||||
Some(message) => message.text.unwrap_or_else(String::new),
|
||||
_ => String::new(),
|
||||
};
|
||||
Ok(ChatInfo {
|
||||
id: self.id,
|
||||
type_: self.typ as u32,
|
||||
name: self.name.clone(),
|
||||
archived: self.archived,
|
||||
param: self.param.to_string(),
|
||||
gossiped_timestamp: self.get_gossiped_timestamp(context),
|
||||
is_sending_locations: self.is_sending_locations,
|
||||
color: self.get_color(context),
|
||||
profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new),
|
||||
subtitle: self.get_subtitle(context),
|
||||
draft,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the chat is archived.
|
||||
pub fn is_archived(&self) -> bool {
|
||||
self.archived
|
||||
@@ -521,68 +497,6 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of a chat.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ChatInfo {
|
||||
/// The chat ID.
|
||||
pub id: u32,
|
||||
|
||||
/// The type of chat as a `u32` representation of [Chattype].
|
||||
///
|
||||
/// On the C API this number is one of the
|
||||
/// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`,
|
||||
/// `DC_CHAT_TYPE_GROUP` or `DC_CHAT_TYPE_VERIFIED_GROUP`
|
||||
/// constants.
|
||||
#[serde(rename = "type")]
|
||||
pub type_: u32,
|
||||
|
||||
/// The name of the chat.
|
||||
pub name: String,
|
||||
|
||||
/// Whether the chat is archived.
|
||||
pub archived: bool,
|
||||
|
||||
/// The "params" of the chat.
|
||||
///
|
||||
/// This is the string-serialised version of [Params] currently.
|
||||
pub param: String,
|
||||
|
||||
/// Something to do with gossiping and timestamps?
|
||||
pub gossiped_timestamp: i64,
|
||||
|
||||
/// Whether this chat is currently sending location-stream messages.
|
||||
pub is_sending_locations: bool,
|
||||
|
||||
/// Colour this chat should be represented in by the UI.
|
||||
///
|
||||
/// Yes, spelling colour is hard.
|
||||
pub color: u32,
|
||||
|
||||
/// The path to the profile image.
|
||||
///
|
||||
/// If there is no profile image set this will be an empty string
|
||||
/// currently.
|
||||
pub profile_image: PathBuf,
|
||||
|
||||
/// Subtitle for the chat.
|
||||
pub subtitle: String,
|
||||
|
||||
/// The draft message text.
|
||||
///
|
||||
/// If the chat has not draft this is an empty string.
|
||||
///
|
||||
/// TODO: This doesn't seem rich enough, it can not handle drafts
|
||||
/// which contain non-text parts. Perhaps it should be a
|
||||
/// simple `has_draft` bool instead.
|
||||
pub draft: String,
|
||||
// ToDo:
|
||||
// - [ ] deaddrop,
|
||||
// - [ ] summary,
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
}
|
||||
|
||||
/// Create a normal chat or a group chat by a messages ID that comes typically
|
||||
/// from the deaddrop, DC_CHAT_ID_DEADDROP (1).
|
||||
///
|
||||
@@ -2076,6 +1990,54 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_info_json(context: &Context, chat_id: u32) -> Result<String, Error> {
|
||||
let chat = Chat::load_from_db(context, chat_id).unwrap();
|
||||
|
||||
// ToDo:
|
||||
// - [x] id
|
||||
// - [x] type
|
||||
// - [x] name
|
||||
// - [x] archived
|
||||
// - [x] color
|
||||
// - [x] profileImage
|
||||
// - [x] subtitle
|
||||
// - [x] draft,
|
||||
// - [ ] deaddrop,
|
||||
// - [ ] summary,
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
|
||||
let profile_image = match chat.get_profile_image(context) {
|
||||
Some(path) => path.into_os_string().into_string().unwrap(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let draft = match get_draft(context, chat_id) {
|
||||
Ok(message) => match message {
|
||||
Some(m) => m.text.unwrap_or_else(|| "".to_string()),
|
||||
None => "".to_string(),
|
||||
},
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
|
||||
let s = json!({
|
||||
"id": chat.id,
|
||||
"type": chat.typ as u32,
|
||||
"name": chat.name,
|
||||
"archived": chat.archived,
|
||||
"param": chat.param.to_string(),
|
||||
"gossiped_timestamp": chat.get_gossiped_timestamp(context),
|
||||
"is_sending_locations": chat.is_sending_locations,
|
||||
"color": chat.get_color(context),
|
||||
"profile_image": profile_image,
|
||||
"subtitle": chat.get_subtitle(context),
|
||||
"draft": draft
|
||||
});
|
||||
|
||||
Ok(s.to_string())
|
||||
}
|
||||
|
||||
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
context
|
||||
.sql
|
||||
@@ -2230,41 +2192,8 @@ pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::contact::Contact;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_chat_info() {
|
||||
let t = dummy_context();
|
||||
let bob = Contact::create(&t.ctx, "bob", "bob@example.com").unwrap();
|
||||
let chat_id = create_by_contact_id(&t.ctx, bob).unwrap();
|
||||
let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap();
|
||||
let info = chat.get_info(&t.ctx).unwrap();
|
||||
|
||||
// Ensure we can serialise this.
|
||||
println!("{}", serde_json::to_string_pretty(&info).unwrap());
|
||||
|
||||
let expected = r#"
|
||||
{
|
||||
"id": 10,
|
||||
"type": 100,
|
||||
"name": "bob",
|
||||
"archived": false,
|
||||
"param": "",
|
||||
"gossiped_timestamp": 0,
|
||||
"is_sending_locations": false,
|
||||
"color": 15895624,
|
||||
"profile_image": "",
|
||||
"subtitle": "bob@example.com",
|
||||
"draft": ""
|
||||
}
|
||||
"#;
|
||||
|
||||
// Ensure we can deserialise this.
|
||||
let loaded: ChatInfo = serde_json::from_str(expected).unwrap();
|
||||
assert_eq!(info, loaded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_draft_no_draft() {
|
||||
let t = dummy_context();
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
@@ -877,15 +901,10 @@ fn create_or_lookup_group(
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
}
|
||||
}
|
||||
// check if the sender is a member of the existing group -
|
||||
// if not, we'll recreate the group list
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id as u32) {
|
||||
// The From-address is not part of this group.
|
||||
// It could be a new user or a DSN from a mailer-daemon.
|
||||
// in any case we do not want to recreate the member list
|
||||
// but still show the message as part of the chat.
|
||||
// After all, the sender has a reference/in-reply-to that
|
||||
// points to this chat.
|
||||
let s = context.stock_str(StockMessage::UnknownSenderForChat);
|
||||
mime_parser.repl_msg_by_error(s.to_string());
|
||||
recreate_member_list = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1518,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) => {
|
||||
@@ -1526,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,
|
||||
@@ -1539,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,
|
||||
@@ -1550,7 +1573,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(contact_ids)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add contacts to database on receiving messages.
|
||||
@@ -1568,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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -63,9 +63,6 @@ pub enum Error {
|
||||
#[fail(display = "IMAP select folder error")]
|
||||
SelectFolderError(#[cause] select_folder::Error),
|
||||
|
||||
#[fail(display = "No mailbox selected, folder: {:?}", _0)]
|
||||
NoMailbox(String),
|
||||
|
||||
#[fail(display = "IMAP other error: {:?}", _0)]
|
||||
Other(String),
|
||||
}
|
||||
@@ -481,10 +478,7 @@ impl Imap {
|
||||
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder);
|
||||
|
||||
let config = self.config.read().await;
|
||||
let mailbox = config
|
||||
.selected_mailbox
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::NoMailbox(folder.to_string()))?;
|
||||
let mailbox = config.selected_mailbox.as_ref().expect("just selected");
|
||||
|
||||
let new_uid_validity = match mailbox.uid_validity {
|
||||
Some(v) => v,
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Client {
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
.expect("failed to read greeting");
|
||||
|
||||
Ok(Client::Secure(client))
|
||||
}
|
||||
@@ -61,7 +61,7 @@ impl Client {
|
||||
let _greeting = client
|
||||
.read_response()
|
||||
.await
|
||||
.ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?;
|
||||
.expect("failed to read greeting");
|
||||
|
||||
Ok(Client::Insecure(client))
|
||||
}
|
||||
|
||||
40
src/job.rs
40
src/job.rs
@@ -201,17 +201,9 @@ impl Job {
|
||||
println!("{}", String::from_utf8_lossy(&body));
|
||||
}
|
||||
match task::block_on(smtp.send(context, recipients_list, body, self.job_id)) {
|
||||
Err(crate::smtp::send::Error::SendTimeout(err)) => {
|
||||
warn!(context, "SMTP send timed out {:?}", err);
|
||||
smtp.disconnect();
|
||||
self.try_again_later(
|
||||
TryAgain::AtOnce,
|
||||
Some("send-timeout".to_string()),
|
||||
);
|
||||
}
|
||||
Err(crate::smtp::send::Error::SendError(err)) => {
|
||||
// Remote error, retry later.
|
||||
warn!(context, "SMTP failed to send: {}", err);
|
||||
info!(context, "SMTP failed to send: {}", err);
|
||||
smtp.disconnect();
|
||||
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
|
||||
}
|
||||
@@ -480,19 +472,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 +596,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 +964,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 => {}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
14
src/log.rs
14
src/log.rs
@@ -7,12 +7,7 @@ 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}",
|
||||
thid = thread.id(),
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
let full = format!("{}:{}: {}", file!(), line!(), &formatted);
|
||||
emit_event!($ctx, $crate::Event::Info(full));
|
||||
}};
|
||||
}
|
||||
@@ -24,12 +19,7 @@ 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}",
|
||||
thid = thread.id(),
|
||||
file = file!(),
|
||||
line = line!(),
|
||||
msg = &formatted);
|
||||
let full = format!("{}:{}: {}", file!(), line!(), &formatted);
|
||||
emit_event!($ctx, $crate::Event::Warning(full));
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1002,21 +1002,6 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns base64-encoded buffer `buf` split into 78-bytes long
|
||||
/// chunks separated by CRLF.
|
||||
///
|
||||
/// This line length limit is an
|
||||
/// [RFC5322 requirement](https://tools.ietf.org/html/rfc5322#section-2.1.1).
|
||||
fn wrapped_base64_encode(buf: &[u8]) -> String {
|
||||
let base64 = base64::encode(&buf);
|
||||
let mut chars = base64.chars();
|
||||
(0..)
|
||||
.map(|_| chars.by_ref().take(78).collect::<String>())
|
||||
.take_while(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\r\n")
|
||||
}
|
||||
|
||||
fn build_body_file(
|
||||
context: &Context,
|
||||
msg: &Message,
|
||||
@@ -1077,7 +1062,7 @@ fn build_body_file(
|
||||
};
|
||||
|
||||
let body = std::fs::read(blob.to_abs_path())?;
|
||||
let encoded_body = wrapped_base64_encode(&body);
|
||||
let encoded_body = base64::encode(&body);
|
||||
|
||||
let mail = PartBuilder::new()
|
||||
.content_type(&mimetype)
|
||||
@@ -1099,7 +1084,7 @@ fn build_selfavatar_file(context: &Context, path: String) -> Result<(PartBuilder
|
||||
None => mime::APPLICATION_OCTET_STREAM,
|
||||
};
|
||||
let body = std::fs::read(blob.to_abs_path())?;
|
||||
let encoded_body = wrapped_base64_encode(&body);
|
||||
let encoded_body = base64::encode(&body);
|
||||
|
||||
let part = PartBuilder::new()
|
||||
.content_type(&mimetype)
|
||||
@@ -1218,13 +1203,4 @@ mod tests {
|
||||
"<123@q> <456@d>".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrapped_base64_encode() {
|
||||
let input = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
let output =
|
||||
"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU\r\n\
|
||||
FBQUFBQUFBQQ==";
|
||||
assert_eq!(wrapped_base64_encode(input), output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,6 @@ pub enum Error {
|
||||
SendError(#[cause] async_smtp::smtp::error::Error),
|
||||
#[fail(display = "SMTP has no transport")]
|
||||
NoTransport,
|
||||
#[fail(display = "SMTP send timed out")]
|
||||
SendTimeout(#[cause] async_std::future::TimeoutError),
|
||||
}
|
||||
|
||||
impl From<async_std::future::TimeoutError> for Error {
|
||||
fn from(err: async_std::future::TimeoutError) -> Error {
|
||||
Error::SendTimeout(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl Smtp {
|
||||
@@ -51,7 +43,6 @@ 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)?;
|
||||
|
||||
@@ -59,6 +50,7 @@ impl Smtp {
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len, recipients_display
|
||||
)));
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
warn!(
|
||||
|
||||
@@ -185,9 +185,6 @@ pub enum StockMessage {
|
||||
Recipients don't need to install Delta Chat, visit websites or sign up anywhere - \
|
||||
however, of course, if they like, you may point them to 👉 https://get.delta.chat"))]
|
||||
WelcomeMessage = 71,
|
||||
|
||||
#[strum(props(fallback = "Unknown Sender for this chat. See 'info' for more details."))]
|
||||
UnknownSenderForChat = 72,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user