mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 23:52:11 +03:00
Compare commits
2 Commits
create_inc
...
no_block_i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9df84d2ee | ||
|
|
53453bb71a |
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,37 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-beta.22
|
||||
|
||||
- #1095 normalize email lineends to CRLF
|
||||
|
||||
- #1095 enable link-time-optimization, saves eg. on android 11 mb
|
||||
|
||||
- #1099 fix import regarding devicechats
|
||||
|
||||
- #1092 improve logging
|
||||
|
||||
- #1096 #1097 #1094 #1090 #1091 internal cleanups
|
||||
|
||||
## 1.0.0-beta.21
|
||||
|
||||
- #1078 #1082 ensure RFC compliance by producing 78 column lines for
|
||||
encoded attachments.
|
||||
|
||||
- #1080 don't recreate and thus break group membership if an unknown
|
||||
sender (or mailer-daemon) sends a message referencing the group chat
|
||||
|
||||
- #1081 #1079 some internal cleanups
|
||||
|
||||
- update imap-proto dependency, to fix yandex/oauth
|
||||
|
||||
## 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
|
||||
|
||||
656
Cargo.lock
generated
656
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -1,13 +1,10 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.22"
|
||||
version = "1.0.0-beta.19"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
|
||||
@@ -17,12 +14,12 @@ hex = "0.4.0"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.7.0"
|
||||
smallvec = "1.0.0"
|
||||
reqwest = "0.10.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" }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", 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" }
|
||||
@@ -58,7 +55,7 @@ bitflags = "1.1.0"
|
||||
debug_stub_derive = "0.3.0"
|
||||
sanitize-filename = "0.2.1"
|
||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||
mailparse = "0.10.2"
|
||||
mailparse = "0.10.1"
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
native-tls = "0.2.3"
|
||||
image = "0.22.3"
|
||||
@@ -85,7 +82,7 @@ path = "examples/repl/main.rs"
|
||||
|
||||
|
||||
[features]
|
||||
default = ["nightly", "ringbuf", "reqwest/blocking", "reqwest/json"]
|
||||
vendored = ["native-tls/vendored", "reqwest/native-tls-vendored"]
|
||||
default = ["nightly", "ringbuf"]
|
||||
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
|
||||
nightly = ["pgp/nightly"]
|
||||
ringbuf = ["pgp/ringbuf"]
|
||||
|
||||
19
README.md
19
README.md
@@ -114,22 +114,3 @@ $ cargo test -- --ignored
|
||||
[circle]: https://circleci.com/gh/deltachat/deltachat-core-rust/
|
||||
[appveyor-shield]: https://ci.appveyor.com/api/projects/status/lqpegel3ld4ipxj8/branch/master?style=flat-square
|
||||
[appveyor]: https://ci.appveyor.com/project/dignifiedquire/deltachat-core-rust/branch/master
|
||||
|
||||
## Language bindings and frontend projects
|
||||
|
||||
Language bindings are available for:
|
||||
|
||||
- [C](https://c.delta.chat)
|
||||
- [Node.js](https://www.npmjs.com/package/deltachat-node)
|
||||
- [Python](https://py.delta.chat)
|
||||
- [Go](https://github.com/hugot/go-deltachat/)
|
||||
- **Java** and **Swift** (contained in the Android/iOS repos)
|
||||
|
||||
The following "frontend" projects make use of the Rust-library
|
||||
or its language bindings:
|
||||
|
||||
- [Android](https://github.com/deltachat/deltachat-android)
|
||||
- [iOS](https://github.com/deltachat/deltachat-ios)
|
||||
- [Desktop](https://github.com/deltachat/deltachat-desktop)
|
||||
- [Pidgin](https://code.ur.gs/lupine/purple-plugin-delta/)
|
||||
- several **Bots**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.22"
|
||||
version = "1.0.0-beta.19"
|
||||
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"]
|
||||
|
||||
@@ -404,14 +404,14 @@ int dc_set_config (dc_context_t* context, const char*
|
||||
char* dc_get_config (dc_context_t* context, const char* key);
|
||||
|
||||
/**
|
||||
* Set stock string translation.
|
||||
* Set stock string translation.
|
||||
*
|
||||
* The function will emit warnings if it returns an error state.
|
||||
* The function will emit warnings if it returns an error state.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object
|
||||
* @param stock_id the integer id of the stock message (DC_STR_*)
|
||||
* @param stock_msg the message to be used
|
||||
* @param stock_msg the message to be used
|
||||
* @return int (==0 on error, 1 on success)
|
||||
*/
|
||||
int dc_set_stock_translation(dc_context_t* context, uint32_t stock_id, const char* stock_msg);
|
||||
@@ -1603,7 +1603,7 @@ char* dc_get_mime_headers (dc_context_t* context, uint32_t ms
|
||||
void dc_delete_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt);
|
||||
|
||||
/**
|
||||
* Empty IMAP server folder: delete all messages.
|
||||
* Empty IMAP server folder: delete all messages.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new()
|
||||
@@ -2618,18 +2618,18 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
|
||||
|
||||
|
||||
/**
|
||||
* Get info summary for a chat, in json format.
|
||||
* Get info summary for a chat, in json format.
|
||||
*
|
||||
* The returned json string has the following key/values:
|
||||
* The returned json string has the following key/values:
|
||||
*
|
||||
* id: chat id
|
||||
* name: chat/group name
|
||||
* color: color of this chat
|
||||
* last-message-from: who sent the last message
|
||||
* last-message-text: message (truncated)
|
||||
* id: chat id
|
||||
* name: chat/group name
|
||||
* color: color of this chat
|
||||
* last-message-from: who sent the last message
|
||||
* last-message-text: message (truncated)
|
||||
* last-message-state: DC_STATE* constant
|
||||
* last-message-date:
|
||||
* avatar-path: path-to-blobfile
|
||||
* last-message-date:
|
||||
* avatar-path: path-to-blobfile
|
||||
* is_verified: yes/no
|
||||
|
||||
* @return a utf8-encoded json string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
|
||||
@@ -4167,30 +4167,30 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||
|
||||
/**
|
||||
* Emitted when an IMAP folder was emptied.
|
||||
* Emitted when an IMAP folder was emptied.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) folder name.
|
||||
* @param data2 (const char*) folder name.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_FOLDER_EMPTIED 106
|
||||
|
||||
/**
|
||||
* Emitted when a new blob file was successfully written
|
||||
* Emitted when a new blob file was successfully written
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
#define DC_EVENT_NEW_BLOB_FILE 150
|
||||
|
||||
/**
|
||||
* Emitted when a blob file was successfully deleted
|
||||
* Emitted when a blob file was successfully deleted
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @return 0
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::location::Location;
|
||||
|
||||
/* * the structure behind dc_array_t */
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum dc_array_t {
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
non_upper_case_globals,
|
||||
non_upper_case_globals,
|
||||
non_camel_case_types,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::expect_fun_call
|
||||
non_snake_case
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate human_panic;
|
||||
extern crate num_traits;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
@@ -123,79 +121,74 @@ impl ContextWrapper {
|
||||
}
|
||||
|
||||
/// Translates the callback from the rust style to the C-style version.
|
||||
unsafe fn translate_cb(&self, event: Event) {
|
||||
if let Some(ffi_cb) = self.cb {
|
||||
let event_id = event.as_id();
|
||||
match event {
|
||||
Event::Info(msg)
|
||||
| Event::SmtpConnected(msg)
|
||||
| Event::ImapConnected(msg)
|
||||
| Event::SmtpMessageSent(msg)
|
||||
| Event::ImapMessageDeleted(msg)
|
||||
| Event::ImapMessageMoved(msg)
|
||||
| Event::ImapFolderEmptied(msg)
|
||||
| Event::NewBlobFile(msg)
|
||||
| Event::DeletedBlobFile(msg)
|
||||
| Event::Warning(msg)
|
||||
| Event::Error(msg)
|
||||
| Event::ErrorNetwork(msg)
|
||||
| Event::ErrorSelfNotInGroup(msg) => {
|
||||
let data2 = CString::new(msg).unwrap_or_default();
|
||||
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t);
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id }
|
||||
| Event::IncomingMsg { chat_id, msg_id }
|
||||
| Event::MsgDelivered { chat_id, msg_id }
|
||||
| Event::MsgFailed { chat_id, msg_id }
|
||||
| Event::MsgRead { chat_id, msg_id } => {
|
||||
ffi_cb(
|
||||
unsafe fn translate_cb(&self, event: Event) -> uintptr_t {
|
||||
match self.cb {
|
||||
Some(ffi_cb) => {
|
||||
let event_id = event.as_id();
|
||||
match event {
|
||||
Event::Info(msg)
|
||||
| Event::SmtpConnected(msg)
|
||||
| Event::ImapConnected(msg)
|
||||
| Event::SmtpMessageSent(msg)
|
||||
| Event::ImapMessageDeleted(msg)
|
||||
| Event::ImapMessageMoved(msg)
|
||||
| Event::ImapFolderEmptied(msg)
|
||||
| Event::NewBlobFile(msg)
|
||||
| Event::DeletedBlobFile(msg)
|
||||
| Event::Warning(msg)
|
||||
| Event::Error(msg)
|
||||
| Event::ErrorNetwork(msg)
|
||||
| Event::ErrorSelfNotInGroup(msg) => {
|
||||
let data2 = CString::new(msg).unwrap_or_default();
|
||||
ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t)
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id }
|
||||
| Event::IncomingMsg { chat_id, msg_id }
|
||||
| Event::MsgDelivered { chat_id, msg_id }
|
||||
| Event::MsgFailed { chat_id, msg_id }
|
||||
| Event::MsgRead { chat_id, msg_id } => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id as uintptr_t,
|
||||
msg_id.to_u32() as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::ChatModified(chat_id) => {
|
||||
ffi_cb(self, event_id, chat_id as uintptr_t, 0);
|
||||
}
|
||||
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
|
||||
let id = id.unwrap_or_default();
|
||||
ffi_cb(self, event_id, id as uintptr_t, 0);
|
||||
}
|
||||
Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => {
|
||||
ffi_cb(self, event_id, progress as uintptr_t, 0);
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
let data1 = file.to_c_string().unwrap_or_default();
|
||||
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0);
|
||||
}
|
||||
Event::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
}
|
||||
| Event::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => {
|
||||
ffi_cb(
|
||||
),
|
||||
Event::ChatModified(chat_id) => ffi_cb(self, event_id, chat_id as uintptr_t, 0),
|
||||
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
|
||||
let id = id.unwrap_or_default();
|
||||
ffi_cb(self, event_id, id as uintptr_t, 0)
|
||||
}
|
||||
Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => {
|
||||
ffi_cb(self, event_id, progress as uintptr_t, 0)
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
let data1 = file.to_c_string().unwrap_or_default();
|
||||
ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0)
|
||||
}
|
||||
Event::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
}
|
||||
| Event::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
contact_id as uintptr_t,
|
||||
progress as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::SecurejoinMemberAdded {
|
||||
chat_id,
|
||||
contact_id,
|
||||
} => {
|
||||
ffi_cb(
|
||||
),
|
||||
Event::SecurejoinMemberAdded {
|
||||
chat_id,
|
||||
contact_id,
|
||||
} => ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id as uintptr_t,
|
||||
contact_id as uintptr_t,
|
||||
);
|
||||
),
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,7 +408,7 @@ fn render_info(
|
||||
) -> std::result::Result<String, std::fmt::Error> {
|
||||
let mut res = String::new();
|
||||
for (key, value) in &info {
|
||||
writeln!(&mut res, "{}={}", key, value)?;
|
||||
write!(&mut res, "{}={}\n", key, value)?;
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
@@ -1346,7 +1339,7 @@ pub unsafe extern "C" fn dc_get_mime_headers(
|
||||
.with_inner(|ctx| {
|
||||
message::get_mime_headers(ctx, MsgId::new(msg_id))
|
||||
.map(|s| s.strdup())
|
||||
.unwrap_or_else(ptr::null_mut)
|
||||
.unwrap_or_else(|| ptr::null_mut())
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
}
|
||||
@@ -1813,7 +1806,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
securejoin::dc_get_securejoin_qr(ctx, chat_id)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.unwrap_or("".to_string())
|
||||
.strdup()
|
||||
})
|
||||
.unwrap_or_else(|_| "".strdup())
|
||||
@@ -2397,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())
|
||||
}
|
||||
@@ -2601,7 +2579,7 @@ pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c
|
||||
if let Some(x) = ffi_msg.message.get_filemime() {
|
||||
x.strdup()
|
||||
} else {
|
||||
dc_strdup(ptr::null())
|
||||
return dc_strdup(ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2993,7 +2971,7 @@ pub unsafe extern "C" fn dc_contact_get_profile_image(
|
||||
.contact
|
||||
.get_profile_image(ctx)
|
||||
.map(|p| p.to_string_lossy().strdup())
|
||||
.unwrap_or_else(std::ptr::null_mut)
|
||||
.unwrap_or_else(|| std::ptr::null_mut())
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {}
|
||||
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
|
||||
()
|
||||
}
|
||||
|
||||
// TODO expose general provider overview url?
|
||||
|
||||
@@ -20,14 +20,14 @@ use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::Event;
|
||||
|
||||
/// Reset database tables.
|
||||
/// Reset database tables. This function is called from Core cmdline.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
|
||||
fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
println!("Resetting tables ({})...", bits);
|
||||
pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
info!(context, "Resetting tables ({})...", bits);
|
||||
if 0 != bits & 1 {
|
||||
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
|
||||
println!("(1) Jobs reset.");
|
||||
info!(context, "(1) Jobs reset.");
|
||||
}
|
||||
if 0 != bits & 2 {
|
||||
sql::execute(
|
||||
@@ -37,11 +37,11 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
params![],
|
||||
)
|
||||
.unwrap();
|
||||
println!("(2) Peerstates reset.");
|
||||
info!(context, "(2) Peerstates reset.");
|
||||
}
|
||||
if 0 != bits & 4 {
|
||||
sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap();
|
||||
println!("(4) Private keypairs reset.");
|
||||
info!(context, "(4) Private keypairs reset.");
|
||||
}
|
||||
if 0 != bits & 8 {
|
||||
sql::execute(
|
||||
@@ -80,7 +80,7 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
)
|
||||
.unwrap();
|
||||
sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap();
|
||||
println!("(8) Rest but server config reset.");
|
||||
info!(context, "(8) Rest but server config reset.");
|
||||
}
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
@@ -155,7 +155,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||
println!("Import: {}", path_plus_name);
|
||||
info!(context, "Import: {}", path_plus_name);
|
||||
if dc_poke_eml_file(context, path_plus_name).is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
@@ -163,7 +163,10 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int {
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
||||
info!(
|
||||
context,
|
||||
"Import: {} items read from \"{}\".", read_cnt, &real_spec
|
||||
);
|
||||
if read_cnt > 0 {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
@@ -187,7 +190,8 @@ fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
};
|
||||
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||
let msgtext = msg.get_text();
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||
prefix.as_ref(),
|
||||
msg.get_id(),
|
||||
@@ -221,14 +225,16 @@ fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||
let mut lines_out = 0;
|
||||
for &msg_id in msglist {
|
||||
if msg_id.is_daymarker() {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"--------------------------------------------------------------------------------"
|
||||
);
|
||||
|
||||
lines_out += 1
|
||||
} else if !msg_id.is_special() {
|
||||
if lines_out == 0 {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"--------------------------------------------------------------------------------",
|
||||
);
|
||||
lines_out += 1
|
||||
@@ -238,7 +244,8 @@ fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
if lines_out > 0 {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"--------------------------------------------------------------------------------"
|
||||
);
|
||||
}
|
||||
@@ -288,7 +295,7 @@ fn log_contactlist(context: &Context, contacts: &Vec<u32>) {
|
||||
);
|
||||
}
|
||||
|
||||
println!("Contact#{}: {}{}", contact_id, line, line2);
|
||||
info!(context, "Contact#{}: {}{}", contact_id, line, line2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,13 +510,15 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
|
||||
let cnt = chatlist.len();
|
||||
if cnt > 0 {
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"================================================================================"
|
||||
);
|
||||
|
||||
for i in (0..cnt).rev() {
|
||||
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}#{}: {} [{} fresh]",
|
||||
chat_prefix(&chat),
|
||||
chat.get_id(),
|
||||
@@ -531,7 +540,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
let timestr = dc_timestamp_to_str(lot.get_timestamp());
|
||||
let text1 = lot.get_text1();
|
||||
let text2 = lot.get_text2();
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}{}{}{} [{}]{}",
|
||||
text1.unwrap_or(""),
|
||||
if text1.is_some() { ": " } else { "" },
|
||||
@@ -544,13 +554,14 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
""
|
||||
},
|
||||
);
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"================================================================================"
|
||||
);
|
||||
}
|
||||
}
|
||||
if location::is_sending_locations_to_chat(context, 0) {
|
||||
println!("Location streaming enabled.");
|
||||
info!(context, "Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
}
|
||||
@@ -578,7 +589,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
} else {
|
||||
format!("{} member(s)", members.len())
|
||||
};
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"{}#{}: {} [{}]{}{}",
|
||||
chat_prefix(sel_chat),
|
||||
sel_chat.get_id(),
|
||||
@@ -681,7 +693,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id());
|
||||
println!("Memberlist:");
|
||||
info!(context, "Memberlist:");
|
||||
|
||||
log_contactlist(context, &contacts);
|
||||
println!(
|
||||
@@ -707,7 +719,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
let default_marker = "-".to_string();
|
||||
for location in &locations {
|
||||
let marker = location.marker.as_ref().unwrap_or(&default_marker);
|
||||
println!(
|
||||
info!(
|
||||
context,
|
||||
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
|
||||
location.location_id,
|
||||
dc_timestamp_to_str(location.timestamp),
|
||||
@@ -721,7 +734,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
|
||||
);
|
||||
}
|
||||
if locations.is_empty() {
|
||||
println!("No locations.");
|
||||
info!(context, "No locations.");
|
||||
}
|
||||
}
|
||||
"sendlocations" => {
|
||||
|
||||
@@ -41,7 +41,7 @@ use self::cmdline::*;
|
||||
|
||||
// Event Handler
|
||||
|
||||
fn receive_event(_context: &Context, event: Event) {
|
||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||
match event {
|
||||
Event::Info(msg) => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
@@ -111,6 +111,8 @@ fn receive_event(_context: &Context, event: Event) {
|
||||
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
// Threads for waiting for messages and for jobs
|
||||
|
||||
@@ -16,18 +16,21 @@ use deltachat::job::{
|
||||
};
|
||||
use deltachat::Event;
|
||||
|
||||
fn cb(_ctx: &Context, event: Event) {
|
||||
fn cb(_ctx: &Context, event: Event) -> usize {
|
||||
print!("[{:?}]", event);
|
||||
|
||||
match event {
|
||||
Event::ConfigureProgress(progress) => {
|
||||
print!(" progress: {}\n", progress);
|
||||
0
|
||||
}
|
||||
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
||||
print!(" {}\n", msg);
|
||||
0
|
||||
}
|
||||
_ => {
|
||||
print!("\n");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,81 +6,8 @@ This package provides bindings to the deltachat-core_ Rust -library
|
||||
which provides imap/smtp/crypto handling as well as chat/group/messages
|
||||
handling to Android, Desktop and IO user interfaces.
|
||||
|
||||
|
||||
Installing bindings from source (Updated: 21-Dec-2019)
|
||||
=========================================================
|
||||
|
||||
Install Rust and Cargo first. Deltachat needs a specific nightly
|
||||
version, the easiest is probably to first install Rust stable from
|
||||
rustup and then use this to install the correct nightly version.
|
||||
|
||||
Install rustup using::
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
|
||||
GIT clone the repo and use rustup to check the correct nightly version
|
||||
is available, if you do not have the right nightly version rustup will
|
||||
download and install it::
|
||||
|
||||
git clone https://github.com/deltachat/deltachat-core-rust
|
||||
cd deltachat-core-rust
|
||||
rustup show
|
||||
|
||||
To install the python bindings make sure you have python installed, a
|
||||
recent 3.x version will also come with the required venv module.
|
||||
E.g. on Debian-based systems `apt install python3 python3-pip
|
||||
python3-venv` should give you a usable python installation. If you
|
||||
prefer you can also
|
||||
`Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_
|
||||
as an alternative to `venv`.
|
||||
|
||||
Ensure you are in the deltachat-core-rust/python directory, create the
|
||||
vivrtual environment and activate it in your shell::
|
||||
|
||||
cd python
|
||||
python3 -m venv venv # or: virtualenv venv
|
||||
source venv/bin/activate
|
||||
|
||||
You should now be able to build the python bindings using the supplied
|
||||
script::
|
||||
|
||||
./install_python_bindings.py
|
||||
|
||||
The installation might take a while, depending on your machine.
|
||||
The bindings will be installed in release mode but with debug symbols.
|
||||
The release mode is necessary because some tests generate RSA keys
|
||||
which is prohibitively slow in debug mode.
|
||||
|
||||
After successful binding installation you can install a few more
|
||||
python packages before finally running the tests::
|
||||
|
||||
python -m pip install pytest pytest-timeout pytest-rerunfailures requests
|
||||
pytest -v tests
|
||||
|
||||
|
||||
running "live" tests (experimental)
|
||||
-----------------------------------
|
||||
|
||||
If you want to run "liveconfig" functional tests you can set
|
||||
``DCC_PY_LIVECONFIG`` to:
|
||||
|
||||
- a particular https-url that you can ask for from the delta
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pw=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
e-mail accounts and run through all functional "liveconfig" tests.
|
||||
|
||||
|
||||
============================================================================================================================
|
||||
(21-Dec-2019) THE BELOW WHEELS ARE CURRENTLY NOT WORKING/BROKEN, COMPILE FROM SOURCE USING ABOVE INSTRUCTIONS INSTEAD
|
||||
============================================================================================================================
|
||||
|
||||
Installing pre-built packages (linux-only) (OUTDATED)
|
||||
========================================================
|
||||
Installing pre-built packages (linux-only)
|
||||
==========================================
|
||||
|
||||
If you have a linux system you may install the ``deltachat`` binary "wheel" package
|
||||
without any "build-from-source" steps.
|
||||
@@ -104,8 +31,8 @@ without any "build-from-source" steps.
|
||||
python -c "import deltachat"
|
||||
|
||||
|
||||
Installing a wheel from a PR/branch (OUTDATED)
|
||||
-------------------------------------------------
|
||||
Installing a wheel from a PR/branch
|
||||
---------------------------------------
|
||||
|
||||
For Linux, we automatically build wheels for all github PR branches
|
||||
and push them to a python package index. To install the latest github ``master`` branch::
|
||||
@@ -119,7 +46,62 @@ and push them to a python package index. To install the latest github ``master``
|
||||
`in contact with us <https://delta.chat/en/contribute>`_.
|
||||
|
||||
|
||||
Installing bindings from source
|
||||
===============================
|
||||
|
||||
If you can't use "binary" method above then you need to compile
|
||||
to core deltachat library::
|
||||
|
||||
git clone https://github.com/deltachat/deltachat-core-rust
|
||||
cd deltachat-core-rust
|
||||
cd python
|
||||
|
||||
If you don't have one active, create and activate a python "virtualenv":
|
||||
|
||||
python virtualenv venv # or python -m venv
|
||||
source venv/bin/activate
|
||||
|
||||
Afterwards ``which python`` tells you that it comes out of the "venv"
|
||||
directory that contains all python install artifacts. Let's first
|
||||
install test tools::
|
||||
|
||||
pip install pytest pytest-timeout pytest-rerunfailures requests
|
||||
|
||||
then cargo-build and install the deltachat bindings::
|
||||
|
||||
python install_python_bindings.py
|
||||
|
||||
The bindings will be installed in release mode but with debug symbols.
|
||||
The release mode is necessary because some tests generate RSA keys
|
||||
which is prohibitively slow in debug mode.
|
||||
|
||||
After successful binding installation you can finally run the tests::
|
||||
|
||||
pytest -v tests
|
||||
|
||||
.. note::
|
||||
|
||||
Some tests are sometimes failing/hanging because of
|
||||
https://github.com/deltachat/deltachat-core-rust/issues/331
|
||||
and
|
||||
https://github.com/deltachat/deltachat-core-rust/issues/326
|
||||
|
||||
|
||||
running "live" tests (experimental)
|
||||
-----------------------------------
|
||||
|
||||
If you want to run "liveconfig" functional tests you can set
|
||||
``DCC_PY_LIVECONFIG`` to:
|
||||
|
||||
- a particular https-url that you can ask for from the delta
|
||||
chat devs.
|
||||
|
||||
- or the path of a file that contains two lines, each describing
|
||||
via "addr=... mail_pw=..." a test account login that will
|
||||
be used for the live tests.
|
||||
|
||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
||||
e-mail accounts and run through all functional "liveconfig" tests.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -722,13 +722,11 @@ class TestOnlineAccount:
|
||||
ac2.mark_seen_messages([msg])
|
||||
|
||||
lp.sec("ac1: waiting for incoming activity")
|
||||
# MDN should be moved even though MDNs are already disabled
|
||||
# wait for MOVED event because even ignored read-receipts should be moved
|
||||
ac1._evlogger.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
|
||||
# MDN is received even though MDNs are already disabled
|
||||
assert msg_out.is_out_mdn_received()
|
||||
assert not msg_out.is_out_mdn_received()
|
||||
|
||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
@@ -1177,46 +1175,29 @@ class TestOnlineAccount:
|
||||
class TestGroupStressTests:
|
||||
def test_group_many_members_add_leave_remove(self, acfactory, lp):
|
||||
lp.sec("creating and configuring five accounts")
|
||||
accounts = [acfactory.get_online_configuring_account() for i in range(5)]
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
accounts = [acfactory.get_online_configuring_account() for i in range(3)]
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
for acc in accounts:
|
||||
wait_configuration_progress(acc, 1000)
|
||||
ac1 = accounts.pop()
|
||||
|
||||
lp.sec("ac1: setting up contacts with 4 other members")
|
||||
contacts = []
|
||||
for acc, name in zip(accounts, list("äöüsr")):
|
||||
contact = ac1.create_contact(acc.get_config("addr"), name=name)
|
||||
contacts.append(contact)
|
||||
|
||||
# make sure we accept the "hi" message
|
||||
ac1.create_chat_by_contact(contact)
|
||||
|
||||
# make sure the other side accepts our messages
|
||||
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
|
||||
chat1 = acc.create_chat_by_contact(c1)
|
||||
|
||||
# send a message to get the contact key via autocrypt header
|
||||
chat1.send_text("hi")
|
||||
msg = ac1.wait_next_incoming_message()
|
||||
assert msg.text == "hi"
|
||||
|
||||
# Save fifth account for later
|
||||
ac5 = accounts.pop()
|
||||
contact5 = contacts.pop()
|
||||
|
||||
lp.sec("ac1: creating group chat with 3 other members")
|
||||
chat = ac1.create_group_chat("title1")
|
||||
for contact in contacts:
|
||||
contacts = []
|
||||
chars = list("äöüsr")
|
||||
for acc in accounts:
|
||||
contact = ac1.create_contact(acc.get_config("addr"), name=chars.pop())
|
||||
contacts.append(contact)
|
||||
chat.add_contact(contact)
|
||||
# make sure the other side accepts our messages
|
||||
c1 = acc.create_contact(ac1.get_config("addr"), "ä member")
|
||||
acc.create_chat_by_contact(c1)
|
||||
|
||||
assert not chat.is_promoted()
|
||||
|
||||
lp.sec("ac1: send mesage to new group chat")
|
||||
msg = chat.send_text("hello")
|
||||
chat.send_text("hello")
|
||||
assert chat.is_promoted()
|
||||
assert msg.is_encrypted()
|
||||
|
||||
gossiped_timestamp = chat.get_summary()["gossiped_timestamp"]
|
||||
assert gossiped_timestamp > 0
|
||||
|
||||
num_contacts = len(chat.get_contacts())
|
||||
assert num_contacts == 3 + 1
|
||||
@@ -1228,49 +1209,14 @@ 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"
|
||||
ac3_contacts = ac3.get_contacts()
|
||||
assert len(ac3_contacts) == 3
|
||||
ac4_contacts = ac3.get_contacts(query=accounts[2].get_config("addr"))
|
||||
assert len(ac4_contacts) == 1
|
||||
|
||||
lp.sec("ac2: removing one contact")
|
||||
to_remove = 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)
|
||||
|
||||
lp.sec("ac1: receiving system message about contact removal")
|
||||
sysmsg = ac1.wait_next_incoming_message()
|
||||
assert to_remove.addr in sysmsg.text
|
||||
assert len(sysmsg.chat.get_contacts()) == 3
|
||||
|
||||
# Receiving message about removed contact does not reset gossip
|
||||
assert chat.get_summary()["gossiped_timestamp"] == gossiped_timestamp
|
||||
|
||||
lp.sec("ac1: sending another message to the chat")
|
||||
chat.send_text("hello2")
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
assert msg.text == "hello2"
|
||||
assert chat.get_summary()["gossiped_timestamp"] == gossiped_timestamp
|
||||
|
||||
lp.sec("ac1: adding fifth member to the chat")
|
||||
chat.add_contact(contact5)
|
||||
# Additng contact to chat resets gossiped_timestamp
|
||||
assert chat.get_summary()["gossiped_timestamp"] >= gossiped_timestamp
|
||||
|
||||
lp.sec("ac2: receiving system message about contact addition")
|
||||
sysmsg = ac2.wait_next_incoming_message()
|
||||
assert contact5.addr in sysmsg.text
|
||||
assert len(sysmsg.chat.get_contacts()) == 4
|
||||
|
||||
lp.sec("ac5: waiting for message about addition to the chat")
|
||||
sysmsg = ac5.wait_next_incoming_message()
|
||||
msg = sysmsg.chat.send_text("hello!")
|
||||
# Message should be encrypted because keys of other members are gossiped
|
||||
assert msg.is_encrypted()
|
||||
|
||||
|
||||
class TestOnlineConfigureFails:
|
||||
def test_invalid_password(self, acfactory):
|
||||
|
||||
370
src/chat.rs
370
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
|
||||
@@ -489,7 +465,7 @@ impl Chat {
|
||||
DC_CONTACT_ID_SELF,
|
||||
to_id as i32,
|
||||
timestamp,
|
||||
msg.viewtype,
|
||||
msg.type_0,
|
||||
msg.state,
|
||||
msg.text.as_ref().map_or("", String::as_str),
|
||||
msg.param.to_string(),
|
||||
@@ -521,108 +497,51 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of a chat.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
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,
|
||||
|
||||
/// Last time this client sent autocrypt gossip headers to this chat.
|
||||
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 chat from a message ID.
|
||||
/// Create a normal chat or a group chat by a messages ID that comes typically
|
||||
/// from the deaddrop, DC_CHAT_ID_DEADDROP (1).
|
||||
///
|
||||
/// Typically you'd do this for a message ID found in the
|
||||
/// [DC_CHAT_ID_DEADDROP] which turns the chat the message belongs to
|
||||
/// into a normal chat. The chat can be a 1:1 chat or a group chat
|
||||
/// and all messages belonging to the chat will be moved from the
|
||||
/// deaddrop to the normal chat.
|
||||
/// If the given message ID already belongs to a normal chat or to a group chat,
|
||||
/// the chat ID of this chat is returned and no new chat is created.
|
||||
/// If a new chat is created, the given message ID is moved to this chat, however,
|
||||
/// there may be more messages moved to the chat from the deaddrop. To get the
|
||||
/// chat messages, use dc_get_chat_msgs().
|
||||
///
|
||||
/// In reality the messages already belong to this chat as receive_imf
|
||||
/// always creates chat IDs appropriately, so this function really
|
||||
/// only unblocks the chat and "scales up" the origin of the contact
|
||||
/// the message is from.
|
||||
/// If the user is asked before creation, he should be
|
||||
/// asked whether he wants to chat with the *contact* belonging to the message;
|
||||
/// the group names may be really weird when taken from the subject of implicit
|
||||
/// groups and this may look confusing.
|
||||
///
|
||||
/// If prompting the user before calling this function, they should be
|
||||
/// asked whether they want to chat with the **contact** the message
|
||||
/// is from and **not** the group name since this can be really weird
|
||||
/// and confusing when taken from subject of implicit groups.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The "created" chat ID is returned.
|
||||
/// Moreover, this function also scales up the origin of the contact belonging
|
||||
/// to the message and, depending on the contacts origin, messages from the
|
||||
/// same group may be shown or not - so, all in all, it is fine to show the
|
||||
/// contact name only.
|
||||
pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result<u32, Error> {
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
let chat = Chat::load_from_db(context, msg.chat_id)?;
|
||||
ensure!(
|
||||
chat.id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"Message can not belong to a special chat"
|
||||
);
|
||||
if chat.blocked != Blocked::Not {
|
||||
unblock(context, chat.id);
|
||||
let mut chat_id = 0;
|
||||
let mut send_event = false;
|
||||
|
||||
// Sending with 0s as data since multiple messages may have changed.
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||
if let Ok(chat) = Chat::load_from_db(context, msg.chat_id) {
|
||||
if chat.id > DC_CHAT_ID_LAST_SPECIAL {
|
||||
chat_id = chat.id;
|
||||
if chat.blocked != Blocked::Not {
|
||||
unblock(context, chat.id);
|
||||
send_event = true;
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, msg.from_id, Origin::CreateChat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if send_event {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, msg.from_id, Origin::CreateChat);
|
||||
Ok(chat.id)
|
||||
|
||||
ensure!(chat_id > 0, "failed to load create chat");
|
||||
|
||||
Ok(chat_id)
|
||||
}
|
||||
|
||||
/// Create a normal chat with a single user. To create group chats,
|
||||
@@ -715,28 +634,6 @@ pub fn update_device_icon(context: &Context) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_special_chat_name(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
stock_id: StockMessage,
|
||||
) -> Result<(), Error> {
|
||||
if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id) {
|
||||
let name: String = context.stock_str(stock_id).into();
|
||||
// the `!= name` condition avoids unneeded writes
|
||||
context.sql.execute(
|
||||
"UPDATE chats SET name=? WHERE id=? AND name!=?;",
|
||||
params![name, chat_id, name],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_special_chat_names(context: &Context) -> Result<(), Error> {
|
||||
update_special_chat_name(context, DC_CONTACT_ID_DEVICE, StockMessage::DeviceMessages)?;
|
||||
update_special_chat_name(context, DC_CONTACT_ID_SELF, StockMessage::SavedMessages)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_or_lookup_by_contact_id(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
@@ -841,18 +738,16 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
|
||||
}
|
||||
|
||||
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
if msg.viewtype == Viewtype::Text {
|
||||
if msg.type_0 == Viewtype::Text {
|
||||
// the caller should check if the message text is empty
|
||||
} else if msgtype_has_file(msg.viewtype) {
|
||||
} else if msgtype_has_file(msg.type_0) {
|
||||
let blob = msg
|
||||
.param
|
||||
.get_blob(Param::File, context, !msg.is_increation())?
|
||||
.ok_or_else(|| {
|
||||
format_err!("Attachment missing for message of type #{}", msg.viewtype)
|
||||
})?;
|
||||
.ok_or_else(|| format_err!("Attachment missing for message of type #{}", msg.type_0))?;
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
|
||||
if msg.viewtype == Viewtype::File || msg.viewtype == Viewtype::Image {
|
||||
if msg.type_0 == Viewtype::File || msg.type_0 == Viewtype::Image {
|
||||
// Correct the type, take care not to correct already very special
|
||||
// formats as GIF or VOICE.
|
||||
//
|
||||
@@ -862,7 +757,7 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
if let Some((better_type, better_mime)) =
|
||||
message::guess_msgtype_from_suffix(&blob.to_abs_path())
|
||||
{
|
||||
msg.viewtype = better_type;
|
||||
msg.type_0 = better_type;
|
||||
msg.param.set(Param::MimeType, better_mime);
|
||||
}
|
||||
} else if !msg.param.exists(Param::MimeType) {
|
||||
@@ -874,10 +769,10 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
context,
|
||||
"Attaching \"{}\" for message type #{}.",
|
||||
blob.to_abs_path().display(),
|
||||
msg.viewtype
|
||||
msg.type_0
|
||||
);
|
||||
} else {
|
||||
bail!("Cannot send messages of type #{}.", msg.viewtype);
|
||||
bail!("Cannot send messages of type #{}.", msg.type_0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1060,7 +955,7 @@ fn maybe_delete_draft(context: &Context, chat_id: u32) -> bool {
|
||||
///
|
||||
/// Return true on success, false on database error.
|
||||
fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<(), Error> {
|
||||
match msg.viewtype {
|
||||
match msg.type_0 {
|
||||
Viewtype::Unknown => bail!("Can not set draft of unknown type."),
|
||||
Viewtype::Text => match msg.text.as_ref() {
|
||||
Some(text) => {
|
||||
@@ -1087,7 +982,7 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<()
|
||||
chat_id as i32,
|
||||
DC_CONTACT_ID_SELF,
|
||||
time(),
|
||||
msg.viewtype,
|
||||
msg.type_0,
|
||||
MessageState::OutDraft,
|
||||
msg.text.as_ref().map(String::as_str).unwrap_or(""),
|
||||
msg.param.to_string(),
|
||||
@@ -1362,7 +1257,7 @@ pub fn get_next_media(
|
||||
if msg_type != Viewtype::Unknown {
|
||||
msg_type
|
||||
} else {
|
||||
msg.viewtype
|
||||
msg.type_0
|
||||
},
|
||||
msg_type2,
|
||||
msg_type3,
|
||||
@@ -1477,7 +1372,7 @@ pub fn get_chat_contacts(context: &Context, chat_id: u32) -> Vec<u32> {
|
||||
/* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a
|
||||
groupchat but the chats stays visible, moreover, this makes displaying lists easier) */
|
||||
|
||||
if chat_id == DC_CHAT_ID_DEADDROP {
|
||||
if chat_id == 1 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -1566,6 +1461,7 @@ pub fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) ->
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn add_contact_to_chat_ex(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
@@ -1639,7 +1535,7 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
}
|
||||
}
|
||||
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
|
||||
msg.viewtype = Viewtype::Text;
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
contact.get_addr(),
|
||||
@@ -1659,7 +1555,6 @@ pub(crate) fn add_contact_to_chat_ex(
|
||||
chat_id,
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -1678,7 +1573,7 @@ fn real_group_exists(context: &Context, chat_id: u32) -> bool {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) -> crate::sql::Result<()> {
|
||||
set_gossiped_timestamp(context, chat_id, 0)
|
||||
}
|
||||
|
||||
@@ -1699,23 +1594,31 @@ pub fn set_gossiped_timestamp(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
timestamp: i64,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"can not add member to special chats"
|
||||
);
|
||||
info!(
|
||||
context,
|
||||
"set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp,
|
||||
);
|
||||
) -> crate::sql::Result<()> {
|
||||
if 0 != chat_id {
|
||||
info!(
|
||||
context,
|
||||
"set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp,
|
||||
);
|
||||
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET gossiped_timestamp=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)?;
|
||||
Ok(())
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET gossiped_timestamp=? WHERE id=?;",
|
||||
params![timestamp, chat_id as i32],
|
||||
)
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"set gossiped_timestamp for all chats to {}.", timestamp,
|
||||
);
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET gossiped_timestamp=?;",
|
||||
params![timestamp],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shall_attach_selfavatar(context: &Context, chat_id: u32) -> Result<bool, Error> {
|
||||
@@ -1800,7 +1703,7 @@ pub fn remove_contact_from_chat(
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
|
||||
if chat.is_promoted() {
|
||||
msg.viewtype = Viewtype::Text;
|
||||
msg.type_0 = Viewtype::Text;
|
||||
if contact.id == DC_CONTACT_ID_SELF {
|
||||
set_group_explicitly_left(context, chat.grpid)?;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
@@ -1904,7 +1807,7 @@ pub fn set_chat_name(
|
||||
.is_ok()
|
||||
{
|
||||
if chat.is_promoted() {
|
||||
msg.viewtype = Viewtype::Text;
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
&chat.name,
|
||||
@@ -1939,6 +1842,7 @@ pub fn set_chat_name(
|
||||
/// The profile image can only be set when you are a member of the
|
||||
/// chat. To remove the profile image pass an empty string for the
|
||||
/// `new_image` parameter.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn set_chat_profile_image(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
@@ -2086,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
|
||||
@@ -2168,7 +2120,7 @@ pub fn add_device_msg(
|
||||
DC_CONTACT_ID_DEVICE,
|
||||
DC_CONTACT_ID_SELF,
|
||||
dc_create_smeared_timestamp(context),
|
||||
msg.viewtype,
|
||||
msg.type_0,
|
||||
MessageState::InFresh,
|
||||
msg.text.as_ref().map_or("", String::as_str),
|
||||
msg.param.to_string(),
|
||||
@@ -2207,22 +2159,6 @@ pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool,
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
// needed on device-switches during export/import;
|
||||
// - deletion in `msgs` with `DC_CONTACT_ID_DEVICE` makes sure,
|
||||
// no wrong information are shown in the device chat
|
||||
// - deletion in `devmsglabels` makes sure,
|
||||
// deleted messages are resetted and useful messages can be added again
|
||||
pub fn delete_and_reset_all_device_msgs(context: &Context) -> Result<(), Error> {
|
||||
context.sql.execute(
|
||||
"DELETE FROM msgs WHERE from_id=?;",
|
||||
params![DC_CONTACT_ID_DEVICE],
|
||||
)?;
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM devmsglabels;", params![])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds an informational message to chat.
|
||||
///
|
||||
/// For example, it can be a message showing that a member was added to a group.
|
||||
@@ -2256,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();
|
||||
@@ -2520,25 +2423,6 @@ mod tests {
|
||||
assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_and_reset_all_device_msgs() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some("message text".to_string());
|
||||
let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap();
|
||||
|
||||
// adding a device message with the same label won't be executed again ...
|
||||
assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap());
|
||||
let msg_id2 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap();
|
||||
assert!(msg_id2.is_unset());
|
||||
|
||||
// ... unless everything is deleted and resetted - as needed eg. on device switch
|
||||
delete_and_reset_all_device_msgs(&t.ctx).unwrap();
|
||||
assert!(!was_device_msg_ever_added(&t.ctx, "some-label").unwrap());
|
||||
let msg_id3 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap();
|
||||
assert_ne!(msg_id1, msg_id3);
|
||||
}
|
||||
|
||||
fn chatlist_len(ctx: &Context, listflags: usize) -> usize {
|
||||
Chatlist::try_load(ctx, listflags, None, None)
|
||||
.unwrap()
|
||||
|
||||
@@ -162,12 +162,6 @@ impl Chatlist {
|
||||
let query = query.trim().to_string();
|
||||
ensure!(!query.is_empty(), "missing query");
|
||||
|
||||
// allow searching over special names that may change at any time
|
||||
// when the ui calls set_stock_translation()
|
||||
if let Err(err) = update_special_chat_names(context) {
|
||||
warn!(context, "cannot update special chat names: {:?}", err)
|
||||
}
|
||||
|
||||
let str_like_cmd = format!("%{}%", query);
|
||||
context.sql.query_map(
|
||||
"SELECT c.id, m.id
|
||||
@@ -390,27 +384,4 @@ mod tests {
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search_special_chat_names() {
|
||||
let t = dummy_context();
|
||||
t.ctx.update_device_chats().unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::job::{self, job_add, job_kill_action};
|
||||
use crate::job::*;
|
||||
use crate::login_param::{CertificateChecks, LoginParam};
|
||||
use crate::oauth2::*;
|
||||
use crate::param::Params;
|
||||
@@ -37,8 +37,8 @@ pub fn configure(context: &Context) {
|
||||
warn!(context, "There is already another ongoing process running.",);
|
||||
return;
|
||||
}
|
||||
job_kill_action(context, job::Action::ConfigureImap);
|
||||
job_add(context, job::Action::ConfigureImap, 0, Params::new(), 0);
|
||||
job_kill_action(context, Action::ConfigureImap);
|
||||
job_add(context, Action::ConfigureImap, 0, Params::new(), 0);
|
||||
}
|
||||
|
||||
/// Check if the context is already configured.
|
||||
@@ -50,15 +50,15 @@ pub fn dc_is_configured(context: &Context) -> bool {
|
||||
* Configure JOB
|
||||
******************************************************************************/
|
||||
#[allow(non_snake_case, unused_must_use)]
|
||||
pub fn JobConfigureImap(context: &Context) -> job::Status {
|
||||
pub fn JobConfigureImap(context: &Context) {
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Cannot configure, database not opened.",);
|
||||
progress!(context, 0);
|
||||
return job::Status::Finished(Err(format_err!("Database not opened")));
|
||||
return;
|
||||
}
|
||||
if !context.alloc_ongoing() {
|
||||
progress!(context, 0);
|
||||
return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process")));
|
||||
return;
|
||||
}
|
||||
let mut success = false;
|
||||
let mut imap_connected_here = false;
|
||||
@@ -441,7 +441,6 @@ pub fn JobConfigureImap(context: &Context) -> job::Status {
|
||||
|
||||
context.free_ongoing();
|
||||
progress!(context, if success { 1000 } else { 0 });
|
||||
job::Status::Finished(Ok(()))
|
||||
}
|
||||
|
||||
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
|
||||
|
||||
@@ -11,10 +11,10 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub fn read_url(context: &Context, url: &str) -> Result<String> {
|
||||
info!(context, "Requesting URL {}", url);
|
||||
|
||||
match reqwest::blocking::Client::new()
|
||||
match reqwest::Client::new()
|
||||
.get(url)
|
||||
.send()
|
||||
.and_then(|res| res.text())
|
||||
.and_then(|mut res| res.text())
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! # Constants
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_camel_case_types, dead_code)]
|
||||
|
||||
use deltachat_derive::*;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -61,6 +61,10 @@ pub const DC_GCL_ADD_SELF: usize = 0x02;
|
||||
// unchanged user avatars are resent to the recipients every some days
|
||||
pub const DC_RESEND_USER_AVATAR_DAYS: i64 = 14;
|
||||
|
||||
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
|
||||
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
|
||||
|
||||
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
||||
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
||||
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
|
||||
@@ -187,7 +191,6 @@ pub const AVATAR_SIZE: u32 = 192;
|
||||
#[repr(i32)]
|
||||
pub enum Viewtype {
|
||||
Unknown = 0,
|
||||
|
||||
/// Text message.
|
||||
/// The text of the message is set using dc_msg_set_text()
|
||||
/// and retrieved with dc_msg_get_text().
|
||||
|
||||
148
src/contact.rs
148
src/contact.rs
@@ -48,26 +48,20 @@ pub struct Contact {
|
||||
///
|
||||
/// Normal contact IDs are larger than these special ones (larger than DC_CONTACT_ID_LAST_SPECIAL).
|
||||
pub id: u32,
|
||||
|
||||
/// Contact name. It is recommended to use `Contact::get_name`,
|
||||
/// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
|
||||
/// May be empty, initially set to `authname`.
|
||||
name: String,
|
||||
|
||||
/// Name authorized by the contact himself. Only this name may be spread to others,
|
||||
/// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
|
||||
/// to access this field.
|
||||
authname: String,
|
||||
|
||||
/// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr`` to access this field.
|
||||
addr: String,
|
||||
|
||||
/// Blocked state. Use dc_contact_is_blocked to access this field.
|
||||
pub blocked: bool,
|
||||
|
||||
blocked: bool,
|
||||
/// The origin/source of the contact.
|
||||
pub origin: Origin,
|
||||
|
||||
/// Parameters as Param::ProfileImage
|
||||
pub param: Params,
|
||||
}
|
||||
@@ -79,53 +73,37 @@ pub struct Contact {
|
||||
#[repr(i32)]
|
||||
pub enum Origin {
|
||||
Unknown = 0,
|
||||
|
||||
/// From: of incoming messages of unknown sender
|
||||
IncomingUnknownFrom = 0x10,
|
||||
|
||||
/// Cc: of incoming messages of unknown sender
|
||||
IncomingUnknownCc = 0x20,
|
||||
|
||||
/// To: of incoming messages of unknown sender
|
||||
IncomingUnknownTo = 0x40,
|
||||
|
||||
/// address scanned but not verified
|
||||
UnhandledQrScan = 0x80,
|
||||
|
||||
/// Reply-To: of incoming message of known sender
|
||||
IncomingReplyTo = 0x100,
|
||||
|
||||
/// Cc: of incoming message of known sender
|
||||
IncomingCc = 0x200,
|
||||
|
||||
/// additional To:'s of incoming message of known sender
|
||||
IncomingTo = 0x400,
|
||||
|
||||
/// a chat was manually created for this user, but no message yet sent
|
||||
CreateChat = 0x800,
|
||||
|
||||
/// message sent by us
|
||||
OutgoingBcc = 0x1000,
|
||||
|
||||
/// message sent by us
|
||||
OutgoingCc = 0x2000,
|
||||
|
||||
/// message sent by us
|
||||
OutgoingTo = 0x4000,
|
||||
|
||||
/// internal use
|
||||
Internal = 0x40000,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// contact added mannually by dc_create_contact(), this should be the largest origin as otherwise the user cannot modify the names
|
||||
/// contact added mannually by dc_create_contact(), this should be the largets origin as otherwise the user cannot modify the names
|
||||
ManuallyCreated = 0x0400_0000,
|
||||
}
|
||||
|
||||
@@ -136,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,30 +391,18 @@ impl Contact {
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
}
|
||||
} else if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin) VALUES(?, ?, ?);",
|
||||
params![name.as_ref(), addr, origin,],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
sth_modified = Modifier::Created;
|
||||
} else {
|
||||
if origin == Origin::IncomingUnknownFrom {
|
||||
update_authname = true;
|
||||
}
|
||||
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
|
||||
params![
|
||||
name.as_ref(),
|
||||
addr,
|
||||
origin,
|
||||
if update_authname { name.as_ref() } else { "" }
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
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.");
|
||||
}
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
|
||||
Ok((row_id, sth_modified))
|
||||
@@ -517,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 },
|
||||
@@ -903,6 +872,22 @@ impl Contact {
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
|
||||
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut bool) -> Origin {
|
||||
let mut ret = Origin::Unknown;
|
||||
*ret_blocked = false;
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
/* we could optimize this by loading only the needed fields */
|
||||
if contact.blocked {
|
||||
*ret_blocked = true;
|
||||
} else {
|
||||
ret = contact.origin;
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
|
||||
if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
return false;
|
||||
@@ -1276,63 +1261,6 @@ mod tests {
|
||||
assert!(!contact.is_blocked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_authnames() {
|
||||
let t = dummy_context();
|
||||
|
||||
let (contact_id, sth_modified) = Contact::add_or_lookup(
|
||||
&t.ctx,
|
||||
"bob1",
|
||||
"bob@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Created);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob1");
|
||||
assert_eq!(contact.get_name(), "bob1");
|
||||
assert_eq!(contact.get_display_name(), "bob1");
|
||||
|
||||
let (contact_id, sth_modified) = Contact::add_or_lookup(
|
||||
&t.ctx,
|
||||
"bob2",
|
||||
"bob@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob2");
|
||||
assert_eq!(contact.get_name(), "bob2");
|
||||
assert_eq!(contact.get_display_name(), "bob2");
|
||||
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(&t.ctx, "bob3", "bob@example.org", Origin::ManuallyCreated)
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob2");
|
||||
assert_eq!(contact.get_name(), "bob3");
|
||||
assert_eq!(contact.get_display_name(), "bob3");
|
||||
|
||||
let (contact_id, sth_modified) = Contact::add_or_lookup(
|
||||
&t.ctx,
|
||||
"bob4",
|
||||
"bob@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob4");
|
||||
assert_eq!(contact.get_name(), "bob3");
|
||||
assert_eq!(contact.get_display_name(), "bob3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr_cmp() {
|
||||
assert!(addr_cmp("AA@AA.ORG", "aa@aa.ORG"));
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
|
||||
use libc::uintptr_t;
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
@@ -17,7 +19,7 @@ use crate::job_thread::JobThread;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::{self, Message, MessengerMessage, MsgId};
|
||||
use crate::message::{self, Message, MsgId};
|
||||
use crate::param::Params;
|
||||
use crate::smtp::Smtp;
|
||||
use crate::sql::Sql;
|
||||
@@ -30,7 +32,12 @@ use crate::sql::Sql;
|
||||
/// * `event` - One of the [Event] items.
|
||||
/// * `data1` - Depends on the event parameter, see [Event].
|
||||
/// * `data2` - Depends on the event parameter, see [Event].
|
||||
pub type ContextCallback = dyn Fn(&Context, Event) -> () + Send + Sync;
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This callback must return 0 unless stated otherwise in the event
|
||||
/// description at [Event].
|
||||
pub type ContextCallback = dyn Fn(&Context, Event) -> uintptr_t + Send + Sync;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
pub struct Context {
|
||||
@@ -79,7 +86,12 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
||||
"sqlite_thread_safe",
|
||||
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
||||
);
|
||||
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
|
||||
res.insert(
|
||||
"arch",
|
||||
(std::mem::size_of::<*mut libc::c_void>())
|
||||
.wrapping_mul(8)
|
||||
.to_string(),
|
||||
);
|
||||
res.insert("level", "awesome".into());
|
||||
res
|
||||
}
|
||||
@@ -160,8 +172,8 @@ impl Context {
|
||||
self.blobdir.as_path()
|
||||
}
|
||||
|
||||
pub fn call_cb(&self, event: Event) {
|
||||
(*self.cb)(self, event);
|
||||
pub fn call_cb(&self, event: Event) -> uintptr_t {
|
||||
(*self.cb)(self, event)
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -440,17 +452,15 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
match msg.is_dc_message {
|
||||
MessengerMessage::No => {}
|
||||
MessengerMessage::Yes | MessengerMessage::Reply => {
|
||||
job_add(
|
||||
self,
|
||||
Action::MoveMsg,
|
||||
msg.id.to_u32() as i32,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
self,
|
||||
Action::MoveMsg,
|
||||
msg.id.to_u32() as i32,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,7 +533,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile);
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOs".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -538,7 +548,7 @@ mod tests {
|
||||
fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap();
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
@@ -549,7 +559,7 @@ mod tests {
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile);
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -559,7 +569,7 @@ mod tests {
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap();
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
@@ -569,7 +579,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = PathBuf::new();
|
||||
let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir);
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -578,7 +588,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir);
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::error::Result;
|
||||
use crate::events::Event;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::job::*;
|
||||
use crate::message::{self, MessageState, MessengerMessage, MsgId};
|
||||
use crate::message::{self, MessageState, MsgId};
|
||||
use crate::mimeparser::*;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
@@ -55,12 +55,15 @@ pub fn dc_receive_imf(
|
||||
println!("{}", String::from_utf8_lossy(imf_raw));
|
||||
}
|
||||
|
||||
let mut mime_parser = MimeMessage::from_bytes(context, imf_raw)?;
|
||||
let mut mime_parser = MimeParser::from_bytes(context, imf_raw)?;
|
||||
|
||||
// we can not add even an empty record if we have no info whatsoever
|
||||
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,37 +102,51 @@ 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 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() {
|
||||
if from_ids.len() > 1 {
|
||||
warn!(
|
||||
context,
|
||||
"mail has more than one From address, only using first: {:?}", field_from
|
||||
);
|
||||
}
|
||||
from_id = from_ids.get_index(0).cloned().unwrap_or_default();
|
||||
if let Ok(contact) = Contact::load_from_db(context, from_id) {
|
||||
incoming_origin = contact.origin;
|
||||
from_id_blocked = contact.blocked;
|
||||
}
|
||||
incoming_origin = Contact::get_origin_by_id(context, from_id, &mut from_id_blocked)
|
||||
} else {
|
||||
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
|
||||
@@ -136,47 +155,39 @@ 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 = mime_parser.get_rfc724_mid().unwrap_or_else(|| {
|
||||
// missing Message-IDs may come if the mail was set from this account with another
|
||||
// client that relies on the SMTP server to generate one.
|
||||
// true eg. for the Webmailer used in all-inkl-KAS
|
||||
create_incoming_rfc724_mid(sent_timestamp, from_id, &to_ids)
|
||||
});
|
||||
let rfc724_mid = match mime_parser.get_rfc724_mid() {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
// missing Message-IDs may come if the mail was set from this account with another
|
||||
// client that relies in the SMTP server to generate one.
|
||||
// true eg. for the Webmailer used in all-inkl-KAS
|
||||
match dc_create_incoming_rfc724_mid(sent_timestamp, from_id, &to_ids) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
bail!("No Message-Id found and could not create incoming rfc724_mid");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if mime_parser.parts.last().is_some() {
|
||||
if let Err(err) = add_parts(
|
||||
context,
|
||||
&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,
|
||||
@@ -243,19 +254,20 @@ pub fn dc_receive_imf(
|
||||
|
||||
fn add_parts(
|
||||
context: &Context,
|
||||
mut mime_parser: &mut MimeMessage,
|
||||
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,
|
||||
@@ -263,12 +275,30 @@ fn add_parts(
|
||||
create_event_to_send: &mut Option<CreateEvent>,
|
||||
) -> Result<()> {
|
||||
let mut state: MessageState;
|
||||
let mut msgrmsg: i32;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut sort_timestamp = 0;
|
||||
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
|
||||
@@ -283,21 +313,18 @@ fn add_parts(
|
||||
bail!("Message already in DB");
|
||||
}
|
||||
|
||||
let mut msgrmsg = if mime_parser.has_chat_version() {
|
||||
MessengerMessage::Yes
|
||||
} else if is_reply_to_messenger_message(context, mime_parser) {
|
||||
MessengerMessage::Reply
|
||||
} else {
|
||||
MessengerMessage::No
|
||||
};
|
||||
// 1 or 0 for yes/no
|
||||
msgrmsg = mime_parser.has_chat_version() as _;
|
||||
if msgrmsg == 0 && is_reply_to_messenger_message(context, mime_parser) {
|
||||
// 2=no, but is reply to messenger message
|
||||
msgrmsg = 2;
|
||||
}
|
||||
// incoming non-chat messages may be discarded;
|
||||
// maybe this can be optimized later, by checking the state before the message body is downloaded
|
||||
let mut allow_creation = true;
|
||||
let show_emails =
|
||||
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default();
|
||||
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
|
||||
&& msgrmsg == MessengerMessage::No
|
||||
{
|
||||
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
|
||||
// this message is a classic email not a chat-message nor a reply to one
|
||||
if show_emails == ShowEmails::Off {
|
||||
*chat_id = DC_CHAT_ID_TRASH;
|
||||
@@ -311,25 +338,23 @@ 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
|
||||
// (eg. contacs may be marked as verified)
|
||||
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||
// avoid discarding by show_emails setting
|
||||
msgrmsg = MessengerMessage::Yes;
|
||||
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;
|
||||
@@ -342,10 +367,6 @@ fn add_parts(
|
||||
needs_stop_ongoing_process = ret.stop_ongoing_process;
|
||||
}
|
||||
Err(err) => {
|
||||
// maybe this message belongs to an aborted scan,
|
||||
// however, by the explicit header check we really know
|
||||
// that it is a Secure-Join message that should be hidden in the chat view.
|
||||
*hidden = true;
|
||||
warn!(
|
||||
context,
|
||||
"Unexpected messaged passed to Secure-Join handshake protocol: {}", err
|
||||
@@ -355,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
|
||||
@@ -375,7 +396,7 @@ fn add_parts(
|
||||
&mut mime_parser,
|
||||
allow_creation,
|
||||
create_blocked,
|
||||
from_id,
|
||||
*from_id,
|
||||
to_ids,
|
||||
)?;
|
||||
*chat_id = new_chat_id;
|
||||
@@ -396,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
|
||||
@@ -407,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;
|
||||
@@ -419,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,8 +461,8 @@ 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()
|
||||
&& msgrmsg == MessengerMessage::No
|
||||
&& !incoming_origin.is_verified()
|
||||
&& msgrmsg == 0
|
||||
&& show_emails != ShowEmails::All
|
||||
{
|
||||
state = MessageState::InNoticed;
|
||||
@@ -459,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;
|
||||
@@ -479,15 +501,14 @@ fn add_parts(
|
||||
}
|
||||
}
|
||||
if *chat_id == 0 && allow_creation {
|
||||
let create_blocked = if MessengerMessage::No != 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;
|
||||
|
||||
@@ -500,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);
|
||||
|
||||
@@ -527,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,
|
||||
@@ -591,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,
|
||||
@@ -647,7 +668,7 @@ fn add_parts(
|
||||
|
||||
fn save_locations(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
mime_parser: &MimeParser,
|
||||
chat_id: u32,
|
||||
from_id: u32,
|
||||
insert_msg_id: MsgId,
|
||||
@@ -746,7 +767,7 @@ fn calc_timestamps(
|
||||
#[allow(non_snake_case)]
|
||||
fn create_or_lookup_group(
|
||||
context: &Context,
|
||||
mime_parser: &mut MimeMessage,
|
||||
mime_parser: &mut MimeParser,
|
||||
allow_creation: bool,
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
@@ -880,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1041,6 +1057,7 @@ fn create_or_lookup_group(
|
||||
}
|
||||
}
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
chat::reset_gossiped_timestamp(context, chat_id)?;
|
||||
}
|
||||
|
||||
if send_EVENT_CHAT_MODIFIED {
|
||||
@@ -1050,7 +1067,7 @@ fn create_or_lookup_group(
|
||||
}
|
||||
|
||||
/// try extract a grpid from a message-id list header value
|
||||
fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<String> {
|
||||
fn extract_grpid(mime_parser: &MimeParser, headerdef: HeaderDef) -> Option<String> {
|
||||
if let Some(value) = mime_parser.get(headerdef) {
|
||||
for part in value.split(',').map(str::trim) {
|
||||
if !part.is_empty() {
|
||||
@@ -1066,7 +1083,7 @@ fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<Stri
|
||||
/// Handle groups for received messages, return chat_id/Blocked status on success
|
||||
fn create_or_lookup_adhoc_group(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
mime_parser: &MimeParser,
|
||||
allow_creation: bool,
|
||||
create_blocked: Blocked,
|
||||
from_id: u32,
|
||||
@@ -1235,6 +1252,7 @@ fn hex_hash(s: impl AsRef<str>) -> String {
|
||||
hex::encode(&result[..8])
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn search_chat_ids_by_contact_ids(
|
||||
context: &Context,
|
||||
unsorted_contact_ids: &[u32],
|
||||
@@ -1302,7 +1320,7 @@ fn search_chat_ids_by_contact_ids(
|
||||
|
||||
fn check_verified_properties(
|
||||
context: &Context,
|
||||
mimeparser: &MimeMessage,
|
||||
mimeparser: &MimeParser,
|
||||
from_id: u32,
|
||||
to_ids: &ContactIds,
|
||||
) -> Result<()> {
|
||||
@@ -1404,7 +1422,7 @@ fn check_verified_properties(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef<str>) {
|
||||
fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
|
||||
let msg = better_msg.as_ref();
|
||||
if !msg.is_empty() && !mime_parser.parts.is_empty() {
|
||||
let part = &mut mime_parser.parts[0];
|
||||
@@ -1414,7 +1432,7 @@ fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef<str>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bool {
|
||||
fn is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> bool {
|
||||
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
|
||||
`In-Reply-To`/`References:` (to support non-Delta-Clients) */
|
||||
|
||||
@@ -1469,7 +1487,7 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: &mailparse::MailAddr) -> b
|
||||
/// - checks also if any of the referenced IDs are send by a messenger
|
||||
/// - it is okay, if the referenced messages are moved to trash here
|
||||
/// - no check for the Chat-* headers (function is only called if it is no messenger message itself)
|
||||
fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -> bool {
|
||||
fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeParser) -> bool {
|
||||
if let Some(value) = mime_parser.get(HeaderDef::InReplyTo) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
|
||||
return true;
|
||||
@@ -1519,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) => {
|
||||
@@ -1527,11 +1546,10 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
};
|
||||
|
||||
let mut contact_ids = ContactIds::new();
|
||||
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,
|
||||
@@ -1540,7 +1558,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,
|
||||
@@ -1551,7 +1569,7 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(contact_ids)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add contacts to database on receiving messages.
|
||||
@@ -1576,19 +1594,20 @@ fn add_or_lookup_contact_by_addr(
|
||||
Ok(row_id)
|
||||
}
|
||||
|
||||
fn create_incoming_rfc724_mid(
|
||||
fn dc_create_incoming_rfc724_mid(
|
||||
message_timestamp: i64,
|
||||
contact_id_from: u32,
|
||||
contact_ids_to: &ContactIds,
|
||||
) -> String {
|
||||
) -> Option<String> {
|
||||
/* create a deterministic rfc724_mid from input such that
|
||||
repeatedly calling it with the same input results in the same Message-id */
|
||||
|
||||
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
|
||||
format!(
|
||||
let result = format!(
|
||||
"{}-{}-{}@stub",
|
||||
message_timestamp, contact_id_from, largest_id_to
|
||||
)
|
||||
);
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1613,7 +1632,7 @@ mod tests {
|
||||
References: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\n\
|
||||
\n\
|
||||
hello\x00";
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
|
||||
let grpid = Some("HcxyMARjyJy".to_string());
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
|
||||
@@ -1628,29 +1647,29 @@ mod tests {
|
||||
References: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\n\
|
||||
\n\
|
||||
hello\x00";
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let grpid = Some("HcxyMARjyJy".to_string());
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid);
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_incoming_rfc724_mid() {
|
||||
fn test_dc_create_incoming_rfc724_mid() {
|
||||
let mut members = ContactIds::new();
|
||||
assert_eq!(
|
||||
create_incoming_rfc724_mid(123, 45, &members),
|
||||
"123-45-0@stub".to_string()
|
||||
dc_create_incoming_rfc724_mid(123, 45, &members),
|
||||
Some("123-45-0@stub".into())
|
||||
);
|
||||
members.insert(7);
|
||||
members.insert(3);
|
||||
assert_eq!(
|
||||
create_incoming_rfc724_mid(123, 45, &members),
|
||||
"123-45-7@stub".to_string()
|
||||
dc_create_incoming_rfc724_mid(123, 45, &members),
|
||||
Some("123-45-7@stub".into())
|
||||
);
|
||||
members.insert(9);
|
||||
assert_eq!(
|
||||
create_incoming_rfc724_mid(123, 45, &members),
|
||||
"123-45-9@stub".to_string()
|
||||
dc_create_incoming_rfc724_mid(123, 45, &members),
|
||||
Some("123-45-9@stub".into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::dehtml::*;
|
||||
|
||||
/// Remove standard (RFC 3676, §4.3) footer if it is found.
|
||||
fn remove_message_footer<'a>(lines: &'a [&str]) -> &'a [&'a str] {
|
||||
for (ix, &line) in lines.iter().enumerate() {
|
||||
@@ -36,9 +38,15 @@ fn split_lines(buf: &str) -> Vec<&str> {
|
||||
|
||||
/// Simplify message text for chat display.
|
||||
/// Remove quotes, signatures, trailing empty lines etc.
|
||||
pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool) {
|
||||
input.retain(|c| c != '\r');
|
||||
let lines = split_lines(&input);
|
||||
pub fn simplify(input: &str, is_html: bool, is_chat_message: bool) -> (String, bool) {
|
||||
let mut out = if is_html {
|
||||
dehtml(input)
|
||||
} else {
|
||||
input.to_string()
|
||||
};
|
||||
|
||||
out.retain(|c| c != '\r');
|
||||
let lines = split_lines(&out);
|
||||
let (lines, is_forwarded) = skip_forward_header(&lines);
|
||||
|
||||
let lines = remove_message_footer(lines);
|
||||
@@ -199,29 +207,61 @@ mod tests {
|
||||
#[test]
|
||||
// proptest does not support [[:graphical:][:space:]] regex.
|
||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
||||
let (output, _is_forwarded) = simplify(input, true);
|
||||
let (output, _is_forwarded) = simplify(&input, false, true);
|
||||
assert!(output.split('\n').all(|s| s != "-- "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_trim() {
|
||||
let input = "line1\n\r\r\rline2".to_string();
|
||||
let (plain, is_forwarded) = simplify(input, false);
|
||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "line1\nline2");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_parse_href() {
|
||||
let html = "<a href=url>text</a";
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "[text](url)");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_bold_text() {
|
||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_forwarded_message() {
|
||||
let input = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here".to_string();
|
||||
let (plain, is_forwarded) = simplify(input, false);
|
||||
let text = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here";
|
||||
let (plain, is_forwarded) = simplify(text, false, false);
|
||||
|
||||
assert_eq!(plain, "Forwarded message");
|
||||
assert!(is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_html_encoded() {
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
let (plain, is_forwarded) = simplify(html, true, false);
|
||||
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
assert!(!is_forwarded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_utilities() {
|
||||
assert!(is_empty_line(" \t"));
|
||||
@@ -260,6 +260,7 @@ pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
|
||||
}
|
||||
|
||||
// the returned suffix is lower-case
|
||||
#[allow(non_snake_case)]
|
||||
pub fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> Option<String> {
|
||||
Path::new(path_filename.as_ref())
|
||||
.extension()
|
||||
@@ -485,7 +486,7 @@ pub(crate) fn dc_get_next_backup_path(
|
||||
pub(crate) fn time() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.unwrap()
|
||||
.as_secs() as i64
|
||||
}
|
||||
|
||||
|
||||
@@ -188,41 +188,4 @@ mod tests {
|
||||
assert_eq!(dehtml(input), output);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_parse_br() {
|
||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(plain, "line1\n\r\r\rline2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_parse_href() {
|
||||
let html = "<a href=url>text</a";
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(plain, "[text](url)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_bold_text() {
|
||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(plain, "text *bold*<>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dehtml_html_encoded() {
|
||||
let html =
|
||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
||||
|
||||
let plain = dehtml(html);
|
||||
|
||||
assert_eq!(
|
||||
plain,
|
||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
18
src/error.rs
18
src/error.rs
@@ -6,52 +6,34 @@ use lettre_email::mime;
|
||||
pub enum Error {
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Failure(failure::Error),
|
||||
|
||||
#[fail(display = "SQL error: {:?}", _0)]
|
||||
SqlError(#[cause] crate::sql::Error),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Io(std::io::Error),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Message(String),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Image(image_meta::ImageError),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Utf8(std::str::Utf8Error),
|
||||
|
||||
#[fail(display = "PGP: {:?}", _0)]
|
||||
Pgp(pgp::errors::Error),
|
||||
|
||||
#[fail(display = "Base64Decode: {:?}", _0)]
|
||||
Base64Decode(base64::DecodeError),
|
||||
|
||||
#[fail(display = "{:?}", _0)]
|
||||
FromUtf8(std::string::FromUtf8Error),
|
||||
|
||||
#[fail(display = "{}", _0)]
|
||||
BlobError(#[cause] crate::blob::BlobError),
|
||||
|
||||
#[fail(display = "Invalid Message ID.")]
|
||||
InvalidMsgId,
|
||||
|
||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
||||
WatchFolderNotFound(String),
|
||||
|
||||
#[fail(display = "Invalid Email: {:?}", _0)]
|
||||
MailParseError(#[cause] mailparse::MailParseError),
|
||||
|
||||
#[fail(display = "Building invalid Email: {:?}", _0)]
|
||||
LettreError(#[cause] lettre_email::error::Error),
|
||||
|
||||
#[fail(display = "SMTP error: {:?}", _0)]
|
||||
SmtpError(#[cause] async_smtp::error::Error),
|
||||
|
||||
#[fail(display = "FromStr error: {:?}", _0)]
|
||||
FromStr(#[cause] mime::FromStrError),
|
||||
|
||||
#[fail(display = "Not Configured")]
|
||||
NotConfigured,
|
||||
}
|
||||
|
||||
@@ -21,38 +21,56 @@ pub enum Event {
|
||||
/// The library-user may write an informational string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "100"))]
|
||||
Info(String),
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "101"))]
|
||||
SmtpConnected(String),
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "102"))]
|
||||
ImapConnected(String),
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "103"))]
|
||||
SmtpMessageSent(String),
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "104"))]
|
||||
ImapMessageDeleted(String),
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "105"))]
|
||||
ImapMessageMoved(String),
|
||||
|
||||
/// Emitted when an IMAP folder was emptied
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "106"))]
|
||||
ImapFolderEmptied(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "150"))]
|
||||
NewBlobFile(String),
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "151"))]
|
||||
DeletedBlobFile(String),
|
||||
|
||||
@@ -60,6 +78,8 @@ pub enum Event {
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "300"))]
|
||||
Warning(String),
|
||||
|
||||
@@ -74,6 +94,8 @@ pub enum Event {
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @return
|
||||
#[strum(props(id = "400"))]
|
||||
Error(String),
|
||||
|
||||
@@ -90,6 +112,8 @@ pub enum Event {
|
||||
/// Moreover, if the UI detects that the device is offline,
|
||||
/// it is probably more useful to report this to the user
|
||||
/// instead of the string from data2.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "401"))]
|
||||
ErrorNetwork(String),
|
||||
|
||||
@@ -98,6 +122,8 @@ pub enum Event {
|
||||
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||
/// dc_send_text_msg() or another sending function.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "410"))]
|
||||
ErrorSelfNotInGroup(String),
|
||||
|
||||
@@ -106,6 +132,8 @@ pub enum Event {
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2000"))]
|
||||
MsgsChanged { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
@@ -113,21 +141,29 @@ pub enum Event {
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2005"))]
|
||||
IncomingMsg { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2010"))]
|
||||
MsgDelivered { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2012"))]
|
||||
MsgFailed { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2015"))]
|
||||
MsgRead { chat_id: u32, msg_id: MsgId },
|
||||
|
||||
@@ -135,12 +171,15 @@ pub enum Event {
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2020"))]
|
||||
ChatModified(u32),
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2030"))]
|
||||
ContactsChanged(Option<u32>),
|
||||
|
||||
@@ -149,12 +188,14 @@ pub enum Event {
|
||||
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2035"))]
|
||||
LocationChanged(Option<u32>),
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @return 0
|
||||
#[strum(props(id = "2041"))]
|
||||
ConfigureProgress(usize),
|
||||
|
||||
@@ -162,6 +203,7 @@ pub enum Event {
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2051"))]
|
||||
ImexProgress(usize),
|
||||
|
||||
@@ -172,6 +214,7 @@ pub enum Event {
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2052"))]
|
||||
ImexFileWritten(PathBuf),
|
||||
|
||||
@@ -187,6 +230,7 @@ pub enum Event {
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2060"))]
|
||||
SecurejoinInviterProgress { contact_id: u32, progress: usize },
|
||||
|
||||
@@ -198,12 +242,14 @@ pub enum Event {
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// This event is sent out to the inviter when a joiner successfully joined a group.
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) contact_id
|
||||
/// @return 0
|
||||
#[strum(props(id = "2062"))]
|
||||
SecurejoinMemberAdded { chat_id: u32, contact_id: u32 },
|
||||
}
|
||||
|
||||
14
src/imex.rs
14
src/imex.rs
@@ -8,7 +8,6 @@ use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat;
|
||||
use crate::chat::delete_and_reset_all_device_msgs;
|
||||
use crate::config::Config;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
@@ -34,19 +33,16 @@ pub enum ImexMode {
|
||||
/// and `private-key-default.asc`, if there are more keys, they are written to files as
|
||||
/// `public-key-<id>.asc` and `private-key-<id>.asc`
|
||||
ExportSelfKeys = 1,
|
||||
|
||||
/// Import private keys found in the directory given as `param1`.
|
||||
/// The last imported key is made the default keys unless its name contains the string `legacy`.
|
||||
/// Public keys are not imported.
|
||||
ImportSelfKeys = 2,
|
||||
|
||||
/// Export a backup to the directory given as `param1`.
|
||||
/// The backup contains all contacts, chats, images and other data and device independent settings.
|
||||
/// The backup does not contain device dependent settings as ringtones or LED notification settings.
|
||||
/// The name of the backup is typically `delta-chat.<day>.bak`, if more than one backup is create on a day,
|
||||
/// the format is `delta-chat.<day>-<number>.bak`
|
||||
ExportBackup = 11,
|
||||
|
||||
/// `param1` is the file (not: directory) to import. The file is normally
|
||||
/// created by DC_IMEX_EXPORT_BACKUP and detected by dc_imex_has_backup(). Importing a backup
|
||||
/// is only possible as long as the context is not configured or used in another way.
|
||||
@@ -137,16 +133,14 @@ fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
|
||||
let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF)?;
|
||||
msg = Message::default();
|
||||
msg.viewtype = Viewtype::File;
|
||||
msg.type_0 = Viewtype::File;
|
||||
msg.param.set(Param::File, setup_file_blob.as_name());
|
||||
|
||||
msg.param
|
||||
.set(Param::MimeType, "application/autocrypt-setup");
|
||||
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
msg.param.set_int(
|
||||
Param::ForcePlaintext,
|
||||
ForcePlaintext::NoAutocryptHeader as i32,
|
||||
);
|
||||
msg.param
|
||||
.set_int(Param::ForcePlaintext, DC_FP_NO_AUTOCRYPT_HEADER);
|
||||
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let msg_id = chat::send_msg(context, chat_id, &mut msg)?;
|
||||
@@ -444,8 +438,6 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
"could not re-open db"
|
||||
);
|
||||
|
||||
delete_and_reset_all_device_msgs(&context)?;
|
||||
|
||||
let total_files_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||
|
||||
600
src/job.rs
600
src/job.rs
@@ -3,7 +3,7 @@
|
||||
//! This module implements a job queue maintained in the SQLite database
|
||||
//! and job types.
|
||||
|
||||
use std::{fmt, time};
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use rand::{thread_rng, Rng};
|
||||
@@ -17,7 +17,7 @@ use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::{Context, PerformJobsNeeded};
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::imap::*;
|
||||
use crate::imex::*;
|
||||
@@ -25,7 +25,7 @@ use crate::location;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::MsgId;
|
||||
use crate::message::{self, Message, MessageState};
|
||||
use crate::mimefactory::{MimeFactory, RenderedEmail};
|
||||
use crate::mimefactory::{vec_contains_lowercase, MimeFactory, RenderedEmail};
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
|
||||
@@ -41,27 +41,11 @@ enum Thread {
|
||||
Smtp = 5000,
|
||||
}
|
||||
|
||||
/// Job try result.
|
||||
#[derive(Debug, Display)]
|
||||
pub enum Status {
|
||||
Finished(std::result::Result<(), Error>),
|
||||
RetryNow,
|
||||
RetryLater,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! job_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
::std::result::Result::Ok(val) => val,
|
||||
::std::result::Result::Err(err) => {
|
||||
return $crate::job::Status::Finished(Err(err.into()));
|
||||
}
|
||||
}
|
||||
};
|
||||
($expr:expr,) => {
|
||||
$crate::job_try!($expr)
|
||||
};
|
||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
enum TryAgain {
|
||||
Dont,
|
||||
AtOnce,
|
||||
StandardDelay,
|
||||
}
|
||||
|
||||
impl Default for Thread {
|
||||
@@ -135,15 +119,10 @@ pub struct Job {
|
||||
pub added_timestamp: i64,
|
||||
pub tries: u32,
|
||||
pub param: Params,
|
||||
try_again: TryAgain,
|
||||
pub pending_error: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Job {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "#{}, action {}", self.job_id, self.action)
|
||||
}
|
||||
}
|
||||
|
||||
impl Job {
|
||||
/// Deletes the job from the database.
|
||||
fn delete(&self, context: &Context) -> bool {
|
||||
@@ -172,164 +151,175 @@ impl Job {
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn SendMsgToSmtp(&mut self, context: &Context) -> Status {
|
||||
fn SendMsgToSmtp(&mut self, context: &Context) {
|
||||
/* connect to SMTP server, if not yet done */
|
||||
if !context.smtp.lock().unwrap().is_connected() {
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
if let Err(err) = context.smtp.lock().unwrap().connect(context, &loginparam) {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
|
||||
if connected.is_err() {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let filename = job_try!(job_try!(self
|
||||
.param
|
||||
.get_path(Param::File, context)
|
||||
.map_err(|_| format_err!("Can't get filename")))
|
||||
.ok_or_else(|| format_err!("Can't get filename")));
|
||||
let body = job_try!(dc_read_file(context, &filename));
|
||||
let recipients = job_try!(self.param.get(Param::Recipients).ok_or_else(|| {
|
||||
warn!(context, "Missing recipients for job {}", self.job_id);
|
||||
format_err!("Missing recipients")
|
||||
}));
|
||||
if let Some(filename) = self.param.get_path(Param::File, context).unwrap_or(None) {
|
||||
if let Ok(body) = dc_read_file(context, &filename) {
|
||||
if let Some(recipients) = self.param.get(Param::Recipients) {
|
||||
let recipients_list = recipients
|
||||
.split('\x1e')
|
||||
.filter_map(
|
||||
|addr| match async_smtp::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let recipients_list = recipients
|
||||
.split('\x1e')
|
||||
.filter_map(
|
||||
|addr| match async_smtp::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||
this happends if dc_delete_msgs() was called
|
||||
before the generated mime was sent out */
|
||||
if 0 != self.foreign_id
|
||||
&& !message::exists(context, MsgId::new(self.foreign_id))
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"Not sending Message {} as it was deleted", self.foreign_id
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
// hold the smtp lock during sending of a job and
|
||||
// its ok/error response processing. Note that if a message
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// otherwise might send it twice.
|
||||
let mut smtp = context.smtp.lock().unwrap();
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
|
||||
info!(context, "smtp-sending out mime message:");
|
||||
println!("{}", String::from_utf8_lossy(&body));
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||
this happends if dc_delete_msgs() was called
|
||||
before the generated mime was sent out */
|
||||
if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)) {
|
||||
return Status::Finished(Err(format_err!(
|
||||
"Not sending Message {} as it was deleted",
|
||||
self.foreign_id
|
||||
)));
|
||||
};
|
||||
|
||||
// hold the smtp lock during sending of a job and
|
||||
// its ok/error response processing. Note that if a message
|
||||
// was sent we need to mark it in the database ASAP as we
|
||||
// otherwise might send it twice.
|
||||
let mut smtp = context.smtp.lock().unwrap();
|
||||
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
|
||||
info!(context, "smtp-sending out mime message:");
|
||||
println!("{}", String::from_utf8_lossy(&body));
|
||||
}
|
||||
match task::block_on(smtp.send(context, recipients_list, body, self.job_id)) {
|
||||
Err(crate::smtp::send::Error::SendError(err)) => {
|
||||
// Remote error, retry later.
|
||||
warn!(context, "SMTP failed to send: {}", err);
|
||||
smtp.disconnect();
|
||||
self.pending_error = Some(err.to_string());
|
||||
Status::RetryLater
|
||||
}
|
||||
Err(crate::smtp::send::Error::EnvelopeError(err)) => {
|
||||
// Local error, job is invalid, do not retry.
|
||||
smtp.disconnect();
|
||||
warn!(context, "SMTP job is invalid: {}", err);
|
||||
Status::Finished(Err(Error::SmtpError(err)))
|
||||
}
|
||||
Err(crate::smtp::send::Error::NoTransport) => {
|
||||
// Should never happen.
|
||||
// It does not even make sense to disconnect here.
|
||||
error!(context, "SMTP job failed because SMTP has no transport");
|
||||
Status::Finished(Err(format_err!("SMTP has not transport")))
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
if 0 != self.foreign_id {
|
||||
set_delivered(context, MsgId::new(self.foreign_id));
|
||||
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);
|
||||
smtp.disconnect();
|
||||
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
|
||||
}
|
||||
Err(crate::smtp::send::Error::EnvelopeError(err)) => {
|
||||
// Local error, job is invalid, do not retry.
|
||||
smtp.disconnect();
|
||||
warn!(context, "SMTP job is invalid: {}", err);
|
||||
}
|
||||
Err(crate::smtp::send::Error::NoTransport) => {
|
||||
// Should never happen.
|
||||
// It does not even make sense to disconnect here.
|
||||
error!(context, "SMTP job failed because SMTP has no transport");
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
if 0 != self.foreign_id {
|
||||
set_delivered(context, MsgId::new(self.foreign_id));
|
||||
}
|
||||
// now also delete the generated file
|
||||
dc_delete_file(context, filename);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(context, "Missing recipients for job {}", self.job_id,);
|
||||
}
|
||||
// now also delete the generated file
|
||||
dc_delete_file(context, filename);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn MoveMsg(&mut self, context: &Context) -> Status {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)));
|
||||
|
||||
if let Err(err) = imap_inbox.ensure_configured_folders(context, true) {
|
||||
warn!(context, "could not configure folders: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
let mut dest_uid = 0;
|
||||
|
||||
match imap_inbox.mv(
|
||||
context,
|
||||
server_folder,
|
||||
msg.server_uid,
|
||||
&dest_folder,
|
||||
&mut dest_uid,
|
||||
) {
|
||||
ImapActionResult::RetryLater => Status::RetryLater,
|
||||
ImapActionResult::Success => {
|
||||
message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
ImapActionResult::Failed => {
|
||||
Status::Finished(Err(format_err!("IMAP action failed")))
|
||||
}
|
||||
ImapActionResult::AlreadyDone => Status::Finished(Ok(())),
|
||||
}
|
||||
} else {
|
||||
Status::Finished(Err(format_err!("No mvbox folder configured")))
|
||||
}
|
||||
// this value does not increase the number of tries
|
||||
fn try_again_later(&mut self, try_again: TryAgain, pending_error: Option<String>) {
|
||||
self.try_again = try_again;
|
||||
self.pending_error = pending_error;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn DeleteMsgOnImap(&mut self, context: &Context) -> Status {
|
||||
fn MoveMsg(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)));
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if let Err(err) = imap_inbox.ensure_configured_folders(context, true) {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
warn!(context, "could not configure folders: {:?}", err);
|
||||
return;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
info!(
|
||||
context,
|
||||
"The message is deleted from the server when all parts are deleted.",
|
||||
);
|
||||
} else {
|
||||
/* if this is the last existing part of the message,
|
||||
we delete the message from the server */
|
||||
let mid = msg.rfc724_mid;
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
let res = imap_inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||
if res == ImapActionResult::RetryLater {
|
||||
// XXX RetryLater is converted to RetryNow here
|
||||
return Status::RetryNow;
|
||||
let mut dest_uid = 0;
|
||||
|
||||
match imap_inbox.mv(
|
||||
context,
|
||||
server_folder,
|
||||
msg.server_uid,
|
||||
&dest_folder,
|
||||
&mut dest_uid,
|
||||
) {
|
||||
ImapActionResult::RetryLater => {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
}
|
||||
ImapActionResult::Success => {
|
||||
message::update_server_uid(
|
||||
context,
|
||||
&msg.rfc724_mid,
|
||||
&dest_folder,
|
||||
dest_uid,
|
||||
);
|
||||
}
|
||||
ImapActionResult::Failed | ImapActionResult::AlreadyDone => {}
|
||||
}
|
||||
}
|
||||
Message::delete_from_db(context, msg.id);
|
||||
Status::Finished(Ok(()))
|
||||
} else {
|
||||
/* eg. device messages have no Message-ID */
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn EmptyServer(&mut self, context: &Context) -> Status {
|
||||
fn DeleteMsgOnImap(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
if let Ok(mut msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
/* eg. device messages have no Message-ID */
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
info!(
|
||||
context,
|
||||
"The message is deleted from the server when all parts are deleted.",
|
||||
);
|
||||
} else {
|
||||
/* if this is the last existing part of the message,
|
||||
we delete the message from the server */
|
||||
let mid = msg.rfc724_mid;
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
let res =
|
||||
imap_inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
|
||||
if res == ImapActionResult::RetryLater {
|
||||
self.try_again_later(TryAgain::AtOnce, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Message::delete_from_db(context, msg.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn EmptyServer(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
|
||||
if let Some(mvbox_folder) = context
|
||||
@@ -342,39 +332,38 @@ impl Job {
|
||||
if self.foreign_id & DC_EMPTY_INBOX > 0 {
|
||||
imap_inbox.empty_folder(context, "INBOX");
|
||||
}
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn MarkseenMsgOnImap(&mut self, context: &Context) -> Status {
|
||||
fn MarkseenMsgOnImap(&mut self, context: &Context) {
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)));
|
||||
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match imap_inbox.set_seen(context, folder, msg.server_uid) {
|
||||
ImapActionResult::RetryLater => Status::RetryLater,
|
||||
ImapActionResult::AlreadyDone => Status::Finished(Ok(())),
|
||||
ImapActionResult::Success | ImapActionResult::Failed => {
|
||||
// XXX the message might just have been moved
|
||||
// we want to send out an MDN anyway
|
||||
// The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
if msg.param.get_bool(Param::WantsMdn).unwrap_or_default()
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
if let Err(err) = send_mdn(context, msg.id) {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
return Status::Finished(Err(err));
|
||||
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match imap_inbox.set_seen(context, folder, msg.server_uid) {
|
||||
ImapActionResult::RetryLater => {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
}
|
||||
ImapActionResult::AlreadyDone => {}
|
||||
ImapActionResult::Success | ImapActionResult::Failed => {
|
||||
// XXX the message might just have been moved
|
||||
// we want to send out an MDN anyway
|
||||
// The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
if 0 != msg.param.get_int(Param::WantsMdn).unwrap_or_default()
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
if let Err(err) = send_mdn(context, msg.id) {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn MarkseenMdnOnImap(&mut self, context: &Context) -> Status {
|
||||
fn MarkseenMdnOnImap(&mut self, context: &Context) {
|
||||
let folder = self
|
||||
.param
|
||||
.get(Param::ServerFolder)
|
||||
@@ -383,12 +372,14 @@ impl Job {
|
||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
||||
let imap_inbox = &context.inbox_thread.read().unwrap().imap;
|
||||
if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
|
||||
return Status::RetryLater;
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
return;
|
||||
}
|
||||
if self.param.get_bool(Param::AlsoMove).unwrap_or_default() {
|
||||
if 0 != self.param.get_int(Param::AlsoMove).unwrap_or_default() {
|
||||
if let Err(err) = imap_inbox.ensure_configured_folders(context, true) {
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
warn!(context, "configuring folders failed: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
return;
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
@@ -398,15 +389,9 @@ impl Job {
|
||||
if ImapActionResult::RetryLater
|
||||
== imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
||||
{
|
||||
Status::RetryLater
|
||||
} else {
|
||||
Status::Finished(Ok(()))
|
||||
self.try_again_later(TryAgain::StandardDelay, None);
|
||||
}
|
||||
} else {
|
||||
Status::Finished(Err(format_err!("MVBOX is not configured")))
|
||||
}
|
||||
} else {
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,7 +571,7 @@ pub fn perform_smtp_idle(context: &Context) {
|
||||
info!(context, "SMTP-idle ended.",);
|
||||
}
|
||||
|
||||
fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
|
||||
let t: i64 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
@@ -596,13 +581,13 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut wakeup_time = time::Duration::new(10 * 60, 0);
|
||||
let mut wakeup_time = Duration::new(10 * 60, 0);
|
||||
let now = time();
|
||||
if t > 0 {
|
||||
if t > now {
|
||||
wakeup_time = time::Duration::new((t - now) as u64, 0);
|
||||
wakeup_time = Duration::new((t - now) as u64, 0);
|
||||
} else {
|
||||
wakeup_time = time::Duration::new(0, 0);
|
||||
wakeup_time = Duration::new(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,7 +633,8 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
}
|
||||
|
||||
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
||||
pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let mut msg = Message::load_from_db(context, msg_id)?;
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
|
||||
@@ -683,12 +669,8 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
let lowercase_from = rendered_msg.from.to_lowercase();
|
||||
if context.get_config_bool(Config::BccSelf)
|
||||
&& !rendered_msg
|
||||
.recipients
|
||||
.iter()
|
||||
.any(|x| x.to_lowercase() == lowercase_from)
|
||||
&& !vec_contains_lowercase(&rendered_msg.recipients, &rendered_msg.from)
|
||||
{
|
||||
rendered_msg.recipients.push(rendered_msg.from.clone());
|
||||
}
|
||||
@@ -759,7 +741,17 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
let jobs: Vec<Job> = load_jobs(context, thread, probe_network);
|
||||
|
||||
for mut job in jobs {
|
||||
info!(context, "{}-job {} started...", thread, job);
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{}, action {} started...",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id,
|
||||
job.action,
|
||||
);
|
||||
|
||||
// some configuration jobs are "exclusive":
|
||||
// - they are always executed in the imap-thread and the smtp-thread is suspended during execution
|
||||
@@ -782,55 +774,40 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
suspend_smtp_thread(context, true);
|
||||
}
|
||||
|
||||
let try_res = (0..2)
|
||||
.map(|tries| {
|
||||
info!(
|
||||
context,
|
||||
"{} performs immediate try {} of job {}", thread, tries, job
|
||||
);
|
||||
for _tries in 0..2 {
|
||||
// this can be modified by a job using dc_job_try_again_later()
|
||||
job.try_again = TryAgain::Dont;
|
||||
|
||||
let try_res = match job.action {
|
||||
Action::Unknown => Status::Finished(Err(format_err!("Unknown job id found"))),
|
||||
Action::SendMsgToSmtp => job.SendMsgToSmtp(context),
|
||||
Action::EmptyServer => job.EmptyServer(context),
|
||||
Action::DeleteMsgOnImap => job.DeleteMsgOnImap(context),
|
||||
Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context),
|
||||
Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context),
|
||||
Action::MoveMsg => job.MoveMsg(context),
|
||||
Action::SendMdn => job.SendMsgToSmtp(context),
|
||||
Action::ConfigureImap => JobConfigureImap(context),
|
||||
Action::ImexImap => match JobImexImap(context, &job) {
|
||||
Ok(()) => Status::Finished(Ok(())),
|
||||
Err(err) => {
|
||||
error!(context, "{}", err);
|
||||
Status::Finished(Err(err))
|
||||
}
|
||||
},
|
||||
Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job),
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::JobMaybeSendLocationsEnded(context, &mut job)
|
||||
match job.action {
|
||||
Action::Unknown => {
|
||||
info!(context, "Unknown job id found");
|
||||
}
|
||||
Action::SendMsgToSmtp => job.SendMsgToSmtp(context),
|
||||
Action::EmptyServer => job.EmptyServer(context),
|
||||
Action::DeleteMsgOnImap => job.DeleteMsgOnImap(context),
|
||||
Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context),
|
||||
Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context),
|
||||
Action::MoveMsg => job.MoveMsg(context),
|
||||
Action::SendMdn => job.SendMsgToSmtp(context),
|
||||
Action::ConfigureImap => JobConfigureImap(context),
|
||||
Action::ImexImap => match JobImexImap(context, &job) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!(context, "{}", err);
|
||||
}
|
||||
Action::Housekeeping => {
|
||||
sql::housekeeping(context);
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
Action::SendMdnOld => Status::Finished(Ok(())),
|
||||
Action::SendMsgToSmtpOld => Status::Finished(Ok(())),
|
||||
};
|
||||
|
||||
info!(
|
||||
context,
|
||||
"{} finished immediate try {} of job {}", thread, tries, job
|
||||
);
|
||||
|
||||
try_res
|
||||
})
|
||||
.find(|try_res| match try_res {
|
||||
Status::RetryNow => false,
|
||||
_ => true,
|
||||
})
|
||||
.unwrap_or(Status::RetryNow);
|
||||
|
||||
},
|
||||
Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job),
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::JobMaybeSendLocationsEnded(context, &mut job)
|
||||
}
|
||||
Action::Housekeeping => sql::housekeeping(context),
|
||||
Action::SendMdnOld => {}
|
||||
Action::SendMsgToSmtpOld => {}
|
||||
}
|
||||
if job.try_again != TryAgain::AtOnce {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
context
|
||||
.sentbox_thread
|
||||
@@ -846,79 +823,54 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
.unsuspend(context);
|
||||
suspend_smtp_thread(context, false);
|
||||
break;
|
||||
}
|
||||
|
||||
match try_res {
|
||||
Status::RetryNow | Status::RetryLater => {
|
||||
let tries = job.tries + 1;
|
||||
|
||||
if tries < JOB_RETRIES {
|
||||
info!(
|
||||
context,
|
||||
"{} thread increases job {} tries to {}", thread, job, tries
|
||||
);
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
job.update(context);
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
thread,
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset
|
||||
);
|
||||
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
context
|
||||
.smtp_state
|
||||
.clone()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} thread removes job {} as it exhausted {} retries",
|
||||
thread,
|
||||
job,
|
||||
JOB_RETRIES
|
||||
);
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
MsgId::new(job.foreign_id),
|
||||
job.pending_error.as_ref(),
|
||||
);
|
||||
}
|
||||
job.delete(context);
|
||||
} else if job.try_again == TryAgain::AtOnce || job.try_again == TryAgain::StandardDelay {
|
||||
let tries = job.tries + 1;
|
||||
if tries < JOB_RETRIES {
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
job.update(context);
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
if thread == Thread::Imap {
|
||||
"INBOX"
|
||||
} else {
|
||||
"SMTP"
|
||||
},
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset
|
||||
);
|
||||
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
context
|
||||
.smtp_state
|
||||
.clone()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.perform_jobs_needed = PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
if !probe_network {
|
||||
continue;
|
||||
}
|
||||
// on dc_maybe_network() we stop trying here;
|
||||
// these jobs are already tried once.
|
||||
// otherwise, we just continue with the next job
|
||||
// to give other jobs a chance being tried at least once.
|
||||
break;
|
||||
}
|
||||
Status::Finished(res) => {
|
||||
if let Err(err) = res {
|
||||
warn!(
|
||||
} else {
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
"{} removes job {} as it failed with error {:?}", thread, job, err
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} removes job {} as it cannot be retried", thread, job
|
||||
MsgId::new(job.foreign_id),
|
||||
job.pending_error.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
job.delete(context);
|
||||
}
|
||||
if !probe_network {
|
||||
continue;
|
||||
}
|
||||
// on dc_maybe_network() we stop trying here;
|
||||
// these jobs are already tried once.
|
||||
// otherwise, we just continue with the next job
|
||||
// to give other jobs a chance being tried at least once.
|
||||
break;
|
||||
} else {
|
||||
job.delete(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -941,12 +893,12 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) {
|
||||
if !context.smtp_state.0.lock().unwrap().doing_jobs {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(time::Duration::from_micros(300 * 1000));
|
||||
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
fn send_mdn(context: &Context, msg_id: MsgId) -> Result<(), Error> {
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
let mimefactory = MimeFactory::from_mdn(context, &msg)?;
|
||||
let rendered_msg = mimefactory.render()?;
|
||||
@@ -956,7 +908,12 @@ fn send_mdn(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_smtp_job(context: &Context, action: Action, rendered_msg: &RenderedEmail) -> Result<()> {
|
||||
#[allow(non_snake_case)]
|
||||
fn add_smtp_job(
|
||||
context: &Context,
|
||||
action: Action,
|
||||
rendered_msg: &RenderedEmail,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(
|
||||
!rendered_msg.recipients.is_empty(),
|
||||
"no recipients for smtp job set"
|
||||
@@ -1075,6 +1032,7 @@ fn load_jobs(context: &Context, thread: Thread, probe_network: bool) -> Vec<Job>
|
||||
added_timestamp: row.get(4)?,
|
||||
tries: row.get(6)?,
|
||||
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
|
||||
try_again: TryAgain::Dont,
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ mod e2ee;
|
||||
mod imap;
|
||||
mod imap_client;
|
||||
pub mod imex;
|
||||
#[macro_use]
|
||||
pub mod job;
|
||||
mod job_thread;
|
||||
pub mod key;
|
||||
@@ -59,7 +58,6 @@ pub mod peerstate;
|
||||
pub mod pgp;
|
||||
pub mod qr;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
pub mod stock;
|
||||
@@ -68,6 +66,7 @@ mod token;
|
||||
mod dehtml;
|
||||
|
||||
pub mod dc_receive_imf;
|
||||
mod dc_simplify;
|
||||
pub mod dc_tools;
|
||||
|
||||
/// if set imap/incoming and smtp/outgoing MIME messages will be printed
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::{self, job_action_exists, job_add, Job};
|
||||
use crate::job::*;
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
@@ -228,7 +228,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, false);
|
||||
job_add(
|
||||
context,
|
||||
job::Action::MaybeSendLocationsEnded,
|
||||
Action::MaybeSendLocationsEnded,
|
||||
chat_id as i32,
|
||||
Params::new(),
|
||||
seconds + 1,
|
||||
@@ -240,14 +240,8 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, force_schedule: bool) {
|
||||
if force_schedule || !job_action_exists(context, job::Action::MaybeSendLocations) {
|
||||
job_add(
|
||||
context,
|
||||
job::Action::MaybeSendLocations,
|
||||
0,
|
||||
Params::new(),
|
||||
60,
|
||||
);
|
||||
if force_schedule || !job_action_exists(context, Action::MaybeSendLocations) {
|
||||
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -548,7 +542,7 @@ pub fn save(
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status {
|
||||
pub fn JobMaybeSendLocations(context: &Context, _job: &Job) {
|
||||
let now = time();
|
||||
let mut continue_streaming = false;
|
||||
info!(
|
||||
@@ -635,40 +629,38 @@ pub fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status {
|
||||
if continue_streaming {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, true);
|
||||
}
|
||||
job::Status::Finished(Ok(()))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Status {
|
||||
pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
let chat_id = job.foreign_id;
|
||||
|
||||
let (send_begin, send_until) = job_try!(context.sql.query_row(
|
||||
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
));
|
||||
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
if !(send_begin == 0 && send_until == 0) {
|
||||
// not streaming, device-message already sent
|
||||
job_try!(context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
));
|
||||
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_info_msg(context, chat_id, stock_str);
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
) {
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
// may happen as several calls to dc_send_locations_to_chat()
|
||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||
if !(send_begin == 0 && send_until == 0) {
|
||||
// not streaming, device-message already sent
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id as i32],
|
||||
).is_ok() {
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_info_msg(context, chat_id, stock_str);
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
job::Status::Finished(Ok(()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -73,28 +73,20 @@ pub enum LotState {
|
||||
// Qr States
|
||||
/// id=contact
|
||||
QrAskVerifyContact = 200,
|
||||
|
||||
/// text1=groupname
|
||||
QrAskVerifyGroup = 202,
|
||||
|
||||
/// id=contact
|
||||
QrFprOk = 210,
|
||||
|
||||
/// id=contact
|
||||
QrFprMissmatch = 220,
|
||||
|
||||
/// test1=formatted fingerprint
|
||||
QrFprWithoutAddr = 230,
|
||||
|
||||
/// id=contact
|
||||
QrAddr = 320,
|
||||
|
||||
/// text1=text
|
||||
QrText = 330,
|
||||
|
||||
/// text1=URL
|
||||
QrUrl = 332,
|
||||
|
||||
/// text1=error string
|
||||
QrError = 400,
|
||||
|
||||
|
||||
@@ -148,22 +148,6 @@ impl rusqlite::types::FromSql for MsgId {
|
||||
#[fail(display = "Invalid Message ID.")]
|
||||
pub struct InvalidMsgId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||
#[repr(u8)]
|
||||
pub enum MessengerMessage {
|
||||
No = 0,
|
||||
Yes = 1,
|
||||
|
||||
/// No, but reply to messenger message.
|
||||
Reply = 2,
|
||||
}
|
||||
|
||||
impl Default for MessengerMessage {
|
||||
fn default() -> Self {
|
||||
Self::No
|
||||
}
|
||||
}
|
||||
|
||||
/// An object representing a single message in memory.
|
||||
/// The message object is not updated.
|
||||
/// If you want an update, you have to recreate the object.
|
||||
@@ -177,7 +161,7 @@ pub struct Message {
|
||||
pub(crate) from_id: u32,
|
||||
pub(crate) to_id: u32,
|
||||
pub(crate) chat_id: u32,
|
||||
pub(crate) viewtype: Viewtype,
|
||||
pub(crate) type_0: Viewtype,
|
||||
pub(crate) state: MessageState,
|
||||
pub(crate) hidden: bool,
|
||||
pub(crate) timestamp_sort: i64,
|
||||
@@ -188,7 +172,8 @@ pub struct Message {
|
||||
pub(crate) in_reply_to: Option<String>,
|
||||
pub(crate) server_folder: Option<String>,
|
||||
pub(crate) server_uid: u32,
|
||||
pub(crate) is_dc_message: MessengerMessage,
|
||||
// TODO: enum
|
||||
pub(crate) is_dc_message: u32,
|
||||
pub(crate) starred: bool,
|
||||
pub(crate) chat_blocked: Blocked,
|
||||
pub(crate) location_id: u32,
|
||||
@@ -198,7 +183,7 @@ pub struct Message {
|
||||
impl Message {
|
||||
pub fn new(viewtype: Viewtype) -> Self {
|
||||
let mut msg = Message::default();
|
||||
msg.viewtype = viewtype;
|
||||
msg.type_0 = viewtype;
|
||||
|
||||
msg
|
||||
}
|
||||
@@ -251,7 +236,7 @@ impl Message {
|
||||
msg.timestamp_sort = row.get("timestamp")?;
|
||||
msg.timestamp_sent = row.get("timestamp_sent")?;
|
||||
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
|
||||
msg.viewtype = row.get("type")?;
|
||||
msg.type_0 = row.get("type")?;
|
||||
msg.state = row.get("state")?;
|
||||
msg.is_dc_message = row.get("msgrmsg")?;
|
||||
|
||||
@@ -327,10 +312,10 @@ impl Message {
|
||||
}
|
||||
|
||||
pub fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> {
|
||||
if chat::msgtype_has_file(self.viewtype) {
|
||||
if chat::msgtype_has_file(self.type_0) {
|
||||
let file_param = self.param.get_path(Param::File, context)?;
|
||||
if let Some(path_and_filename) = file_param {
|
||||
if (self.viewtype == Viewtype::Image || self.viewtype == Viewtype::Gif)
|
||||
if (self.type_0 == Viewtype::Image || self.type_0 == Viewtype::Gif)
|
||||
&& !self.param.exists(Param::Width)
|
||||
{
|
||||
self.param.set_int(Param::Width, 0);
|
||||
@@ -409,7 +394,7 @@ impl Message {
|
||||
}
|
||||
|
||||
pub fn get_viewtype(&self) -> Viewtype {
|
||||
self.viewtype
|
||||
self.type_0
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> MessageState {
|
||||
@@ -489,7 +474,7 @@ impl Message {
|
||||
|
||||
pub fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String {
|
||||
get_summarytext_by_raw(
|
||||
self.viewtype,
|
||||
self.type_0,
|
||||
self.text.as_ref(),
|
||||
&self.param,
|
||||
approx_characters,
|
||||
@@ -533,11 +518,11 @@ impl Message {
|
||||
/// copied to the blobdir. Thus those attachments need to be
|
||||
/// created immediately in the blobdir with a valid filename.
|
||||
pub fn is_increation(&self) -> bool {
|
||||
chat::msgtype_has_file(self.viewtype) && self.state == MessageState::OutPreparing
|
||||
chat::msgtype_has_file(self.type_0) && self.state == MessageState::OutPreparing
|
||||
}
|
||||
|
||||
pub fn is_setupmessage(&self) -> bool {
|
||||
if self.viewtype != Viewtype::File {
|
||||
if self.type_0 != Viewtype::File {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -702,7 +687,7 @@ impl Lot {
|
||||
}
|
||||
|
||||
self.text2 = Some(get_summarytext_by_raw(
|
||||
msg.viewtype,
|
||||
msg.type_0,
|
||||
msg.text.as_ref(),
|
||||
&msg.param,
|
||||
SUMMARY_CHARACTERS,
|
||||
@@ -823,9 +808,9 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
ret += &format!("\nFile: {}, {}, bytes\n", path.display(), bytes);
|
||||
}
|
||||
|
||||
if msg.viewtype != Viewtype::Text {
|
||||
if msg.type_0 != Viewtype::Text {
|
||||
ret += "Type: ";
|
||||
ret += &format!("{}", msg.viewtype);
|
||||
ret += &format!("{}", msg.type_0);
|
||||
ret += "\n";
|
||||
ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
|
||||
}
|
||||
@@ -859,7 +844,6 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
|
||||
"mp3" => (Viewtype::Audio, "audio/mpeg"),
|
||||
"aac" => (Viewtype::Audio, "audio/aac"),
|
||||
"mp4" => (Viewtype::Video, "video/mp4"),
|
||||
"webm" => (Viewtype::Video, "video/webm"),
|
||||
"jpg" => (Viewtype::Image, "image/jpeg"),
|
||||
"jpeg" => (Viewtype::Image, "image/jpeg"),
|
||||
"jpe" => (Viewtype::Image, "image/jpeg"),
|
||||
|
||||
@@ -18,30 +18,29 @@ use crate::param::*;
|
||||
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Loaded {
|
||||
Message { chat: Chat },
|
||||
Message,
|
||||
MDN,
|
||||
}
|
||||
|
||||
/// Helper to construct mime messages.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct MimeFactory<'a, 'b> {
|
||||
from_addr: String,
|
||||
from_displayname: String,
|
||||
selfstatus: String,
|
||||
|
||||
/// Vector of pairs of recipient name and address
|
||||
recipients: Vec<(String, String)>,
|
||||
|
||||
timestamp: i64,
|
||||
loaded: Loaded,
|
||||
msg: &'b Message,
|
||||
increation: bool,
|
||||
in_reply_to: String,
|
||||
references: String,
|
||||
req_mdn: bool,
|
||||
context: &'a Context,
|
||||
pub from_addr: String,
|
||||
pub from_displayname: String,
|
||||
pub selfstatus: String,
|
||||
pub recipients_names: Vec<String>,
|
||||
pub recipients_addr: Vec<String>,
|
||||
pub timestamp: i64,
|
||||
pub loaded: Loaded,
|
||||
pub msg: &'b Message,
|
||||
pub chat: Option<Chat>,
|
||||
pub increation: bool,
|
||||
pub in_reply_to: String,
|
||||
pub references: String,
|
||||
pub req_mdn: bool,
|
||||
pub context: &'a Context,
|
||||
last_added_location_id: u32,
|
||||
attach_selfavatar: bool,
|
||||
}
|
||||
@@ -72,22 +71,44 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
) -> Result<MimeFactory<'a, 'b>, Error> {
|
||||
let chat = Chat::load_from_db(context, msg.chat_id)?;
|
||||
|
||||
let from_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
let from_displayname = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
let mut recipients = Vec::with_capacity(5);
|
||||
let mut req_mdn = false;
|
||||
let mut factory = MimeFactory {
|
||||
from_addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
recipients_names: Vec::with_capacity(5),
|
||||
recipients_addr: Vec::with_capacity(5),
|
||||
timestamp: msg.timestamp_sort,
|
||||
loaded: Loaded::Message,
|
||||
msg,
|
||||
chat: Some(chat),
|
||||
increation: msg.is_increation(),
|
||||
in_reply_to: String::default(),
|
||||
references: String::default(),
|
||||
req_mdn: false,
|
||||
last_added_location_id: 0,
|
||||
attach_selfavatar: add_selfavatar,
|
||||
context,
|
||||
};
|
||||
|
||||
// just set the chat above
|
||||
let chat = factory.chat.as_ref().unwrap();
|
||||
|
||||
if chat.is_self_talk() {
|
||||
recipients.push((from_displayname.to_string(), from_addr.to_string()));
|
||||
factory
|
||||
.recipients_names
|
||||
.push(factory.from_displayname.to_string());
|
||||
factory.recipients_addr.push(factory.from_addr.to_string());
|
||||
} else {
|
||||
context.sql.query_map(
|
||||
"SELECT c.authname, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![msg.chat_id as i32],
|
||||
params![factory.msg.chat_id as i32],
|
||||
|row| {
|
||||
let authname: String = row.get(0)?;
|
||||
let addr: String = row.get(1)?;
|
||||
@@ -96,15 +117,17 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
if !recipients_contain_addr(&recipients, &addr) {
|
||||
recipients.push((authname, addr));
|
||||
if !vec_contains_lowercase(&factory.recipients_addr, &addr) {
|
||||
factory.recipients_addr.push(addr);
|
||||
factory.recipients_names.push(authname);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
let command = msg.param.get_cmd();
|
||||
let command = factory.msg.param.get_cmd();
|
||||
let msg = &factory.msg;
|
||||
|
||||
/* for added members, the list is just fine */
|
||||
if command == SystemMessage::MemberRemovedFromGroup {
|
||||
@@ -116,19 +139,20 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
|
||||
if !email_to_remove.is_empty()
|
||||
&& !addr_cmp(email_to_remove, self_addr)
|
||||
&& !recipients_contain_addr(&recipients, &email_to_remove)
|
||||
&& !vec_contains_lowercase(&factory.recipients_addr, &email_to_remove)
|
||||
{
|
||||
recipients.push(("".to_string(), email_to_remove.to_string()));
|
||||
factory.recipients_names.push("".to_string());
|
||||
factory.recipients_addr.push(email_to_remove.to_string());
|
||||
}
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
&& command != SystemMessage::SecurejoinMessage
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
{
|
||||
req_mdn = true;
|
||||
factory.req_mdn = true;
|
||||
}
|
||||
}
|
||||
let (in_reply_to, references) = context.sql.query_row(
|
||||
let row = context.sql.query_row(
|
||||
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
|
||||
params![msg.id],
|
||||
|row| {
|
||||
@@ -140,26 +164,21 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
render_rfc724_mid_list(&references),
|
||||
))
|
||||
},
|
||||
)?;
|
||||
);
|
||||
|
||||
match row {
|
||||
Ok((in_reply_to, references)) => {
|
||||
factory.in_reply_to = in_reply_to;
|
||||
factory.references = references;
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"mimefactory: failed to load mime_in_reply_to: {:?}", err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let factory = MimeFactory {
|
||||
from_addr,
|
||||
from_displayname,
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
recipients,
|
||||
timestamp: msg.timestamp_sort,
|
||||
loaded: Loaded::Message { chat },
|
||||
msg,
|
||||
increation: msg.is_increation(),
|
||||
in_reply_to,
|
||||
references,
|
||||
req_mdn,
|
||||
last_added_location_id: 0,
|
||||
attach_selfavatar: add_selfavatar,
|
||||
context,
|
||||
};
|
||||
Ok(factory)
|
||||
}
|
||||
|
||||
@@ -187,13 +206,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
recipients: vec![(
|
||||
contact.get_authname().to_string(),
|
||||
contact.get_addr().to_string(),
|
||||
)],
|
||||
recipients_names: vec![contact.get_authname().to_string()],
|
||||
recipients_addr: vec![contact.get_addr().to_string()],
|
||||
timestamp: dc_create_smeared_timestamp(context),
|
||||
loaded: Loaded::MDN,
|
||||
msg,
|
||||
chat: None,
|
||||
increation: false,
|
||||
in_reply_to: String::default(),
|
||||
references: String::default(),
|
||||
@@ -210,10 +228,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
.ok_or_else(|| format_err!("Not configured"))?;
|
||||
|
||||
Ok(self
|
||||
.recipients
|
||||
.recipients_addr
|
||||
.iter()
|
||||
.filter(|(_, addr)| addr != &self_addr)
|
||||
.map(|(_, addr)| {
|
||||
.filter(|addr| *addr != &self_addr)
|
||||
.map(|addr| {
|
||||
(
|
||||
Peerstate::from_addr(self.context, &self.context.sql, addr),
|
||||
addr.as_str(),
|
||||
@@ -223,9 +241,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
fn is_e2ee_guranteed(&self) -> bool {
|
||||
match &self.loaded {
|
||||
Loaded::Message { chat } => {
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
match self.loaded {
|
||||
Loaded::Message => {
|
||||
if self.chat.as_ref().unwrap().typ == Chattype::VerifiedGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -251,8 +269,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
fn min_verified(&self) -> PeerstateVerifiedStatus {
|
||||
match &self.loaded {
|
||||
Loaded::Message { chat } => {
|
||||
match self.loaded {
|
||||
Loaded::Message => {
|
||||
let chat = self.chat.as_ref().unwrap();
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
PeerstateVerifiedStatus::BidirectVerified
|
||||
} else {
|
||||
@@ -264,8 +283,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
fn should_force_plaintext(&self) -> i32 {
|
||||
match &self.loaded {
|
||||
Loaded::Message { chat } => {
|
||||
match self.loaded {
|
||||
Loaded::Message => {
|
||||
let chat = self.chat.as_ref().unwrap();
|
||||
if chat.typ == Chattype::VerifiedGroup {
|
||||
0
|
||||
} else {
|
||||
@@ -275,16 +295,17 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
Loaded::MDN => ForcePlaintext::NoAutocryptHeader as i32,
|
||||
Loaded::MDN => DC_FP_NO_AUTOCRYPT_HEADER,
|
||||
}
|
||||
}
|
||||
|
||||
fn should_do_gossip(&self) -> bool {
|
||||
match &self.loaded {
|
||||
Loaded::Message { chat } => {
|
||||
match self.loaded {
|
||||
Loaded::Message => {
|
||||
let chat = self.chat.as_ref().unwrap();
|
||||
// beside key- and member-changes, force re-gossip every 48 hours
|
||||
let gossiped_timestamp = chat.get_gossiped_timestamp(self.context);
|
||||
if time() > gossiped_timestamp + (2 * 24 * 60 * 60) {
|
||||
if gossiped_timestamp == 0 || (gossiped_timestamp + (2 * 24 * 60 * 60)) > time() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -295,8 +316,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
fn grpimage(&self) -> Option<String> {
|
||||
match &self.loaded {
|
||||
Loaded::Message { chat } => {
|
||||
match self.loaded {
|
||||
Loaded::Message => {
|
||||
let chat = self.chat.as_ref().unwrap();
|
||||
let cmd = self.msg.param.get_cmd();
|
||||
|
||||
match cmd {
|
||||
@@ -326,33 +348,39 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
|
||||
fn subject_str(&self) -> String {
|
||||
match self.loaded {
|
||||
Loaded::Message { ref chat } => {
|
||||
let raw = message::get_summarytext_by_raw(
|
||||
self.msg.viewtype,
|
||||
self.msg.text.as_ref(),
|
||||
&self.msg.param,
|
||||
32,
|
||||
self.context,
|
||||
);
|
||||
let mut lines = raw.lines();
|
||||
let raw_subject = if let Some(line) = lines.next() {
|
||||
line
|
||||
} else {
|
||||
""
|
||||
};
|
||||
Loaded::Message => {
|
||||
match self.chat {
|
||||
Some(ref chat) => {
|
||||
let raw = message::get_summarytext_by_raw(
|
||||
self.msg.type_0,
|
||||
self.msg.text.as_ref(),
|
||||
&self.msg.param,
|
||||
32,
|
||||
self.context,
|
||||
);
|
||||
let mut lines = raw.lines();
|
||||
let raw_subject = if let Some(line) = lines.next() {
|
||||
line
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let afwd_email = self.msg.param.exists(Param::Forwarded);
|
||||
let fwd = if afwd_email { "Fwd: " } else { "" };
|
||||
let afwd_email = self.msg.param.exists(Param::Forwarded);
|
||||
let fwd = if afwd_email { "Fwd: " } else { "" };
|
||||
|
||||
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
// do not add the "Chat:" prefix for setup messages
|
||||
self.context
|
||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||
.into_owned()
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
format!("Chat: {}: {}{}", chat.name, fwd, raw_subject)
|
||||
} else {
|
||||
format!("Chat: {}{}", fwd, raw_subject)
|
||||
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
// do not add the "Chat:" prefix for setup messages
|
||||
self.context
|
||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||
.into_owned()
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup
|
||||
{
|
||||
format!("Chat: {}: {}{}", chat.name, fwd, raw_subject)
|
||||
} else {
|
||||
format!("Chat: {}{}", fwd, raw_subject)
|
||||
}
|
||||
}
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
Loaded::MDN => {
|
||||
@@ -377,8 +405,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
self.from_addr.clone(),
|
||||
);
|
||||
|
||||
let mut to = Vec::with_capacity(self.recipients.len());
|
||||
for (name, addr) in self.recipients.iter() {
|
||||
let mut to = Vec::with_capacity(self.recipients_names.len());
|
||||
let name_iter = self.recipients_names.iter();
|
||||
let addr_iter = self.recipients_addr.iter();
|
||||
for (name, addr) in name_iter.zip(addr_iter) {
|
||||
if name.is_empty() {
|
||||
to.push(Address::new_mailbox(addr.clone()));
|
||||
} else {
|
||||
@@ -441,13 +471,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let subject = encode_words(&subject_str);
|
||||
|
||||
let mut message = match self.loaded {
|
||||
Loaded::Message { .. } => {
|
||||
Loaded::Message => {
|
||||
self.render_message(&mut protected_headers, &mut unprotected_headers, &grpimage)?
|
||||
}
|
||||
Loaded::MDN => self.render_mdn()?,
|
||||
};
|
||||
|
||||
if force_plaintext != ForcePlaintext::NoAutocryptHeader as i32 {
|
||||
if force_plaintext != DC_FP_NO_AUTOCRYPT_HEADER {
|
||||
// unless determined otherwise we add the Autocrypt header
|
||||
let aheader = encrypt_helper.get_aheader().to_string();
|
||||
unprotected_headers.push(Header::new("Autocrypt".into(), aheader));
|
||||
@@ -461,7 +491,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let is_encrypted = should_encrypt && force_plaintext == 0;
|
||||
|
||||
let rfc724_mid = match self.loaded {
|
||||
Loaded::Message { .. } => self.msg.rfc724_mid.clone(),
|
||||
Loaded::Message => self.msg.rfc724_mid.clone(),
|
||||
Loaded::MDN => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
|
||||
};
|
||||
|
||||
@@ -571,7 +601,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
};
|
||||
|
||||
let MimeFactory {
|
||||
recipients,
|
||||
recipients_addr,
|
||||
from_addr,
|
||||
last_added_location_id,
|
||||
msg,
|
||||
@@ -586,10 +616,10 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
is_gossiped,
|
||||
last_added_location_id,
|
||||
foreign_id: match loaded {
|
||||
Loaded::Message { .. } => Some(msg.id),
|
||||
Loaded::Message => Some(msg.id),
|
||||
Loaded::MDN => None,
|
||||
},
|
||||
recipients: recipients.into_iter().map(|(_, addr)| addr).collect(),
|
||||
recipients: recipients_addr,
|
||||
from: from_addr,
|
||||
rfc724_mid,
|
||||
})
|
||||
@@ -602,10 +632,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
grpimage: &Option<String>,
|
||||
) -> Result<PartBuilder, Error> {
|
||||
let context = self.context;
|
||||
let chat = match &self.loaded {
|
||||
Loaded::Message { chat } => chat,
|
||||
Loaded::MDN => bail!("Attempt to render MDN as a message"),
|
||||
};
|
||||
let chat = self.chat.as_ref().unwrap();
|
||||
let command = self.msg.param.get_cmd();
|
||||
let mut placeholdertext = None;
|
||||
let mut meta_part = None;
|
||||
@@ -734,7 +761,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
if let Some(grpimage) = grpimage {
|
||||
info!(self.context, "setting group image '{}'", grpimage);
|
||||
let mut meta = Message::default();
|
||||
meta.viewtype = Viewtype::Image;
|
||||
meta.type_0 = Viewtype::Image;
|
||||
meta.param.set(Param::File, grpimage);
|
||||
|
||||
let (mail, filename_as_sent) = build_body_file(context, &meta, "group-image")?;
|
||||
@@ -753,15 +780,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.msg.viewtype == Viewtype::Sticker {
|
||||
if self.msg.type_0 == Viewtype::Sticker {
|
||||
protected_headers.push(Header::new("Chat-Content".into(), "sticker".into()));
|
||||
}
|
||||
|
||||
if self.msg.viewtype == Viewtype::Voice
|
||||
|| self.msg.viewtype == Viewtype::Audio
|
||||
|| self.msg.viewtype == Viewtype::Video
|
||||
if self.msg.type_0 == Viewtype::Voice
|
||||
|| self.msg.type_0 == Viewtype::Audio
|
||||
|| self.msg.type_0 == Viewtype::Video
|
||||
{
|
||||
if self.msg.viewtype == Viewtype::Voice {
|
||||
if self.msg.type_0 == Viewtype::Voice {
|
||||
protected_headers.push(Header::new("Chat-Voice-Message".into(), "1".into()));
|
||||
}
|
||||
let duration_ms = self.msg.param.get_int(Param::Duration).unwrap_or_default();
|
||||
@@ -817,7 +844,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
.body(message_text)];
|
||||
|
||||
// add attachment part
|
||||
if chat::msgtype_has_file(self.msg.viewtype) {
|
||||
if chat::msgtype_has_file(self.msg.type_0) {
|
||||
if !is_file_size_okay(context, &self.msg) {
|
||||
bail!(
|
||||
"Message exceeds the recommended {} MB.",
|
||||
@@ -867,7 +894,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
)
|
||||
.header((
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"location.kml\"",
|
||||
"attachment; filename=\"message.kml\"",
|
||||
))
|
||||
.body(kml_content),
|
||||
);
|
||||
@@ -912,7 +939,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
/// Render an MDN
|
||||
fn render_mdn(&mut self) -> Result<PartBuilder, Error> {
|
||||
// RFC 6522, this also requires the `report-type` parameter which is equal
|
||||
// to the MIME subtype of the second body part of the multipart/report
|
||||
// to the MIME subtype of the second body part of the multipart/report */
|
||||
//
|
||||
// currently, we do not send MDNs encrypted:
|
||||
// - in a multi-device-setup that is not set up properly, MDNs would disturb the communication as they
|
||||
@@ -975,20 +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();
|
||||
std::iter::repeat_with(|| 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,
|
||||
@@ -1004,7 +1017,7 @@ fn build_body_file(
|
||||
// not transfer the original filenames eg. for images; these names
|
||||
// are normally not needed and contain timestamps, running numbers
|
||||
// etc.
|
||||
let filename_to_send: String = match msg.viewtype {
|
||||
let filename_to_send: String = match msg.type_0 {
|
||||
Viewtype::Voice => chrono::Utc
|
||||
.timestamp(msg.timestamp_sort as i64, 0)
|
||||
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", &suffix))
|
||||
@@ -1049,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)
|
||||
@@ -1071,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)
|
||||
@@ -1085,11 +1098,14 @@ fn build_selfavatar_file(context: &Context, path: String) -> Result<(PartBuilder
|
||||
Ok((part, filename_to_send))
|
||||
}
|
||||
|
||||
fn recipients_contain_addr(recipients: &[(String, String)], addr: &str) -> bool {
|
||||
let addr_lc = addr.to_lowercase();
|
||||
recipients
|
||||
.iter()
|
||||
.any(|(_, cur)| cur.to_lowercase() == addr_lc)
|
||||
pub(crate) fn vec_contains_lowercase(vec: &[String], part: &str) -> bool {
|
||||
let partlc = part.to_lowercase();
|
||||
for cur in vec.iter() {
|
||||
if cur.to_lowercase() == partlc {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_file_size_okay(context: &Context, msg: &Message) -> bool {
|
||||
@@ -1187,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::config::Config;
|
||||
use crate::constants::Viewtype;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_simplify::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::dehtml::dehtml;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::events::Event;
|
||||
@@ -22,21 +22,11 @@ use crate::message;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::simplify::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::{bail, ensure};
|
||||
|
||||
/// A parsed MIME message.
|
||||
///
|
||||
/// This represents the relevant information of a parsed MIME message
|
||||
/// for deltachat. The original MIME message might have had more
|
||||
/// information but this representation should contain everything
|
||||
/// needed for deltachat's purposes.
|
||||
///
|
||||
/// It is created by parsing the raw data of an actual MIME message
|
||||
/// using the [MimeMessage::from_bytes] constructor.
|
||||
#[derive(Debug)]
|
||||
pub struct MimeMessage<'a> {
|
||||
pub struct MimeParser<'a> {
|
||||
pub context: &'a Context,
|
||||
pub parts: Vec<Part>,
|
||||
header: HashMap<String, String>,
|
||||
@@ -50,6 +40,7 @@ pub struct MimeMessage<'a> {
|
||||
pub user_avatar: AvatarAction,
|
||||
pub group_avatar: AvatarAction,
|
||||
reports: Vec<Report>,
|
||||
parsed_protected_headers: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -91,11 +82,11 @@ impl Default for SystemMessage {
|
||||
|
||||
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
|
||||
|
||||
impl<'a> MimeMessage<'a> {
|
||||
impl<'a> MimeParser<'a> {
|
||||
pub fn from_bytes(context: &'a Context, body: &[u8]) -> Result<Self> {
|
||||
let mail = mailparse::parse_mail(body)?;
|
||||
|
||||
let mut parser = MimeMessage {
|
||||
let mut parser = MimeParser {
|
||||
parts: Vec::new(),
|
||||
header: Default::default(),
|
||||
decrypting_failed: false,
|
||||
@@ -111,6 +102,7 @@ impl<'a> MimeMessage<'a> {
|
||||
message_kml: None,
|
||||
user_avatar: AvatarAction::None,
|
||||
group_avatar: AvatarAction::None,
|
||||
parsed_protected_headers: false,
|
||||
};
|
||||
|
||||
let message_time = mail
|
||||
@@ -588,12 +580,7 @@ impl<'a> MimeMessage<'a> {
|
||||
("".into(), false)
|
||||
} else {
|
||||
let is_html = mime_type == mime::TEXT_HTML;
|
||||
let out = if is_html {
|
||||
dehtml(&decoded_data)
|
||||
} else {
|
||||
decoded_data.clone()
|
||||
};
|
||||
simplify(out, self.has_chat_version())
|
||||
simplify(&decoded_data, is_html, self.has_chat_version())
|
||||
};
|
||||
|
||||
if !simplified_txt.is_empty() {
|
||||
@@ -764,7 +751,7 @@ impl<'a> MimeMessage<'a> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handle reports (only MDNs for now)
|
||||
// Handle reports (only MDNs for now)
|
||||
pub fn handle_reports(
|
||||
&self,
|
||||
from_id: u32,
|
||||
@@ -775,28 +762,34 @@ impl<'a> MimeMessage<'a> {
|
||||
if self.reports.is_empty() {
|
||||
return;
|
||||
}
|
||||
// If a user disabled MDNs we do not show pending incoming ones anymore
|
||||
// but we do want them to potentially get moved from the INBOX still.
|
||||
let mdns_enabled = self.context.get_config_bool(Config::MdnsEnabled);
|
||||
|
||||
let mut mdn_recognized = false;
|
||||
for report in &self.reports {
|
||||
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
|
||||
self.context,
|
||||
from_id,
|
||||
&report.original_message_id,
|
||||
sent_timestamp,
|
||||
) {
|
||||
self.context.call_cb(Event::MsgRead { chat_id, msg_id });
|
||||
mdn_recognized = true;
|
||||
}
|
||||
}
|
||||
let mut mdn_recognized = false;
|
||||
|
||||
if self.has_chat_version() || mdn_recognized {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if self.has_chat_version() && self.context.get_config_bool(Config::MvboxMove) {
|
||||
param.set_int(Param::AlsoMove, 1);
|
||||
if mdns_enabled {
|
||||
if let Some((chat_id, msg_id)) = message::mdn_from_ext(
|
||||
self.context,
|
||||
from_id,
|
||||
&report.original_message_id,
|
||||
sent_timestamp,
|
||||
) {
|
||||
self.context.call_cb(Event::MsgRead { chat_id, msg_id });
|
||||
mdn_recognized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if self.has_chat_version() || mdn_recognized || !mdns_enabled {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if self.has_chat_version() && self.context.get_config_bool(Config::MvboxMove) {
|
||||
param.set_int(Param::AlsoMove, 1);
|
||||
}
|
||||
job_add(self.context, Action::MarkseenMdnOnImap, 0, param, 0);
|
||||
}
|
||||
job_add(self.context, Action::MarkseenMdnOnImap, 0, param, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -826,11 +819,7 @@ fn update_gossip_peerstates(
|
||||
})));
|
||||
}
|
||||
|
||||
if recipients
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&header.addr.to_lowercase())
|
||||
{
|
||||
if recipients.as_ref().unwrap().contains(&header.addr) {
|
||||
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
peerstate.apply_gossip(header, message_time);
|
||||
@@ -986,7 +975,7 @@ fn get_attachment_filename(mail: &mailparse::ParsedMail) -> Result<String> {
|
||||
Ok(desired_filename)
|
||||
}
|
||||
|
||||
// returned addresses are normalized and lowercased.
|
||||
// returned addresses are normalized.
|
||||
fn get_recipients<S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> HashSet<String> {
|
||||
let mut recipients: HashSet<String> = Default::default();
|
||||
|
||||
@@ -998,11 +987,11 @@ fn get_recipients<S: AsRef<str>, T: Iterator<Item = (S, S)>>(headers: T) -> Hash
|
||||
for addr in addrs.iter() {
|
||||
match addr {
|
||||
mailparse::MailAddr::Single(ref info) => {
|
||||
recipients.insert(addr_normalize(&info.addr).to_lowercase());
|
||||
recipients.insert(addr_normalize(&info.addr).into());
|
||||
}
|
||||
mailparse::MailAddr::Group(ref infos) => {
|
||||
for info in &infos.addrs {
|
||||
recipients.insert(addr_normalize(&info.addr).to_lowercase());
|
||||
recipients.insert(addr_normalize(&info.addr).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1042,7 +1031,7 @@ mod tests {
|
||||
fn test_dc_mimeparser_crash() {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(mimeparser.get_subject(), None);
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
@@ -1055,7 +1044,7 @@ mod tests {
|
||||
let context = dummy_context();
|
||||
|
||||
// just don't crash
|
||||
let _ = MimeMessage::from_bytes(&context.ctx, data.as_bytes());
|
||||
let _ = MimeParser::from_bytes(&context.ctx, data.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1063,7 +1052,7 @@ mod tests {
|
||||
fn test_get_rfc724_mid_exists() {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/mail_with_message_id.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
mimeparser.get_rfc724_mid(),
|
||||
@@ -1075,7 +1064,7 @@ mod tests {
|
||||
fn test_get_rfc724_mid_not_exists() {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(mimeparser.get_rfc724_mid(), None);
|
||||
}
|
||||
|
||||
@@ -1083,7 +1072,7 @@ mod tests {
|
||||
fn test_get_recipients() {
|
||||
let context = dummy_context();
|
||||
let raw = include_bytes!("../test-data/message/mail_with_cc.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::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"));
|
||||
@@ -1130,14 +1119,14 @@ mod tests {
|
||||
fn test_parse_first_addr() {
|
||||
let context = dummy_context();
|
||||
let raw = b"From: hello@one.org, world@two.org\n\
|
||||
Chat-Disposition-Notification-To: wrong\n\
|
||||
Content-Type: text/plain\n\
|
||||
Chat-Disposition-Notification-To: wrong
|
||||
Content-Type: text/plain;
|
||||
Chat-Version: 1.0\n\
|
||||
\n\
|
||||
test1\n\
|
||||
";
|
||||
\x00";
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
let of = mimeparser.parse_first_addr(HeaderDef::From_).unwrap();
|
||||
assert_eq!(of, mailparse::addrparse("hello@one.org").unwrap()[0]);
|
||||
@@ -1165,9 +1154,10 @@ mod tests {
|
||||
test1\n\
|
||||
\n\
|
||||
--==break==--\n\
|
||||
\n";
|
||||
\n\
|
||||
\x00";
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
// non-overwritten headers do not bubble up
|
||||
let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap();
|
||||
@@ -1192,26 +1182,26 @@ mod tests {
|
||||
let t = dummy_context();
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_attach_txt.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(mimeparser.user_avatar, AvatarAction::None);
|
||||
assert_eq!(mimeparser.group_avatar, AvatarAction::None);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_avatar.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
|
||||
assert!(mimeparser.user_avatar.is_change());
|
||||
assert_eq!(mimeparser.group_avatar, AvatarAction::None);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_avatar_deleted.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
|
||||
assert_eq!(mimeparser.user_avatar, AvatarAction::Delete);
|
||||
assert_eq!(mimeparser.group_avatar, AvatarAction::None);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
|
||||
assert!(mimeparser.user_avatar.is_change());
|
||||
@@ -1221,53 +1211,10 @@ mod tests {
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml");
|
||||
let raw = String::from_utf8_lossy(raw).to_string();
|
||||
let raw = raw.replace("Chat-User-Avatar:", "Xhat-Xser-Xvatar:");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, raw.as_bytes()).unwrap();
|
||||
let mimeparser = MimeParser::from_bytes(&t.ctx, raw.as_bytes()).unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Image);
|
||||
assert_eq!(mimeparser.user_avatar, AvatarAction::None);
|
||||
assert!(mimeparser.group_avatar.is_change());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mimeparser_message_kml() {
|
||||
let context = dummy_context();
|
||||
let raw = b"Chat-Version: 1.0\n\
|
||||
From: foo <foo@example.org>\n\
|
||||
To: bar <bar@example.org>\n\
|
||||
Subject: Location streaming\n\
|
||||
Content-Type: multipart/mixed; boundary=\"==break==\"\n\
|
||||
\n\
|
||||
\n\
|
||||
--==break==\n\
|
||||
Content-Type: text/plain; charset=utf-8\n\
|
||||
\n\
|
||||
--\n\
|
||||
Sent with my Delta Chat Messenger: https://delta.chat\n\
|
||||
\n\
|
||||
--==break==\n\
|
||||
Content-Type: application/vnd.google-earth.kml+xml\n\
|
||||
Content-Disposition: attachment; filename=\"message.kml\"\n\
|
||||
\n\
|
||||
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||
<Document addr=\"foo@example.org\">\n\
|
||||
<Placemark><Timestamp><when>XXX</when></Timestamp><Point><coordinates accuracy=\"48\">0.0,0.0</coordinates></Point></Placemark>\n\
|
||||
</Document>\n\
|
||||
</kml>\n\
|
||||
\n\
|
||||
--==break==--\n\
|
||||
;";
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
assert_eq!(
|
||||
mimeparser.get_subject(),
|
||||
Some("Location streaming".to_string())
|
||||
);
|
||||
assert!(mimeparser.location_kml.is_none());
|
||||
assert!(mimeparser.message_kml.is_some());
|
||||
|
||||
// There is only one part because message.kml attachment is special
|
||||
// and only goes into message_kml.
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
}
|
||||
|
||||
// ... and POST
|
||||
let response = reqwest::blocking::Client::new()
|
||||
let response = reqwest::Client::new()
|
||||
.post(post_url)
|
||||
.form(&post_param)
|
||||
.send();
|
||||
@@ -165,7 +165,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let response = response.unwrap();
|
||||
let mut response = response.unwrap();
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
@@ -271,8 +271,7 @@ impl Oauth2 {
|
||||
{
|
||||
match domain {
|
||||
"gmail.com" | "googlemail.com" => Some(OAUTH2_GMAIL),
|
||||
"yandex.com" | "yandex.by" | "yandex.kz" | "yandex.ru" | "yandex.ua" | "ya.ru"
|
||||
| "narod.ru" => Some(OAUTH2_YANDEX),
|
||||
"yandex.com" | "yandex.ru" | "yandex.ua" => Some(OAUTH2_YANDEX),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
@@ -291,12 +290,12 @@ impl Oauth2 {
|
||||
// "verified_email": true,
|
||||
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
|
||||
// }
|
||||
let response = reqwest::blocking::Client::new().get(&userinfo_url).send();
|
||||
let response = reqwest::Client::new().get(&userinfo_url).send();
|
||||
if response.is_err() {
|
||||
warn!(context, "Error getting userinfo: {:?}", response);
|
||||
return None;
|
||||
}
|
||||
let response = response.unwrap();
|
||||
let mut response = response.unwrap();
|
||||
if !response.status().is_success() {
|
||||
warn!(context, "Error getting userinfo: {:?}", response.status());
|
||||
return None;
|
||||
|
||||
44
src/param.rs
44
src/param.rs
@@ -16,51 +16,36 @@ use crate::mimeparser::SystemMessage;
|
||||
pub enum Param {
|
||||
/// For messages and jobs
|
||||
File = b'f',
|
||||
|
||||
/// For Messages
|
||||
Width = b'w',
|
||||
|
||||
/// For Messages
|
||||
Height = b'h',
|
||||
|
||||
/// For Messages
|
||||
Duration = b'd',
|
||||
|
||||
/// For Messages
|
||||
MimeType = b'm',
|
||||
|
||||
/// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send
|
||||
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||
GuaranteeE2ee = b'c',
|
||||
|
||||
/// For Messages: decrypted with validation errors or without mutual set, if neither
|
||||
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
|
||||
ErroneousE2ee = b'e',
|
||||
|
||||
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
|
||||
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
|
||||
ForcePlaintext = b'u',
|
||||
|
||||
/// For Messages
|
||||
WantsMdn = b'r',
|
||||
|
||||
/// For Messages
|
||||
Forwarded = b'a',
|
||||
|
||||
/// For Messages
|
||||
Cmd = b'S',
|
||||
|
||||
/// For Messages
|
||||
Arg = b'E',
|
||||
|
||||
/// For Messages
|
||||
Arg2 = b'F',
|
||||
|
||||
/// For Messages
|
||||
Arg3 = b'G',
|
||||
|
||||
/// For Messages
|
||||
Arg4 = b'H',
|
||||
|
||||
/// For Messages
|
||||
Error = b'L',
|
||||
|
||||
@@ -77,44 +62,31 @@ pub enum Param {
|
||||
/// When the original message is then finally sent this parameter
|
||||
/// is used to also send all the forwarded messages.
|
||||
PrepForwards = b'P',
|
||||
|
||||
/// For Jobs
|
||||
SetLatitude = b'l',
|
||||
|
||||
/// For Jobs
|
||||
SetLongitude = b'n',
|
||||
|
||||
/// For Jobs
|
||||
ServerFolder = b'Z',
|
||||
|
||||
/// For Jobs
|
||||
ServerUid = b'z',
|
||||
|
||||
/// For Jobs
|
||||
AlsoMove = b'M',
|
||||
|
||||
/// For Jobs: space-separated list of message recipients
|
||||
Recipients = b'R',
|
||||
|
||||
/// For Groups
|
||||
// For Groups
|
||||
Unpromoted = b'U',
|
||||
|
||||
/// For Groups and Contacts
|
||||
// For Groups and Contacts
|
||||
ProfileImage = b'i',
|
||||
|
||||
/// For Chats
|
||||
// For Chats
|
||||
Selftalk = b'K',
|
||||
|
||||
/// For Chats
|
||||
// For Chats
|
||||
Devicetalk = b'D',
|
||||
|
||||
/// For QR
|
||||
// For QR
|
||||
Auth = b's',
|
||||
|
||||
/// For QR
|
||||
// For QR
|
||||
GroupId = b'x',
|
||||
|
||||
/// For QR
|
||||
// For QR
|
||||
GroupName = b'g',
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::fmt;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::aheader::*;
|
||||
use crate::chat::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::key::*;
|
||||
@@ -94,7 +95,6 @@ pub enum ToSave {
|
||||
pub enum DegradeEvent {
|
||||
/// Recoverable by an incoming encrypted mail.
|
||||
EncryptionPaused = 0x01,
|
||||
|
||||
/// Recoverable by a new verify.
|
||||
FingerprintChanged = 0x02,
|
||||
}
|
||||
@@ -417,6 +417,7 @@ impl<'a> Peerstate<'a> {
|
||||
&self.addr,
|
||||
],
|
||||
)?;
|
||||
reset_gossiped_timestamp(self.context, 0)?;
|
||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||
sql::execute(
|
||||
self.context,
|
||||
|
||||
30
src/qr.rs
30
src/qr.rs
@@ -69,7 +69,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
(fp, &rest[1..])
|
||||
}) {
|
||||
Some(pair) => pair,
|
||||
None => (payload, ""),
|
||||
None => return format_err!("Invalid OPENPGP4FPR found").into(),
|
||||
};
|
||||
|
||||
// replace & with \n to match expected param format
|
||||
@@ -83,11 +83,11 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
|
||||
let addr = if let Some(addr) = param.get(Param::Forwarded) {
|
||||
match normalize_address(addr) {
|
||||
Ok(addr) => Some(addr),
|
||||
Ok(addr) => addr,
|
||||
Err(err) => return err.into(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
return format_err!("Missing address").into();
|
||||
};
|
||||
|
||||
// what is up with that param name?
|
||||
@@ -157,7 +157,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
lot.state = LotState::QrFprWithoutAddr;
|
||||
lot.text1 = Some(dc_format_fingerprint(&fingerprint));
|
||||
}
|
||||
} else if let Some(addr) = addr {
|
||||
} else {
|
||||
if grpid.is_some() && grpname.is_some() {
|
||||
lot.state = LotState::QrAskVerifyGroup;
|
||||
lot.text1 = grpname;
|
||||
@@ -172,8 +172,6 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
lot.fingerprint = Some(fingerprint);
|
||||
lot.invitenumber = invitenumber;
|
||||
lot.auth = auth;
|
||||
} else {
|
||||
return format_err!("Missing address").into();
|
||||
}
|
||||
|
||||
lot
|
||||
@@ -473,24 +471,4 @@ mod tests {
|
||||
assert_eq!(contact.get_addr(), "cli@deltachat.de");
|
||||
assert_eq!(contact.get_name(), "Jörn P. P.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_openpgp_without_addr() {
|
||||
let ctx = dummy_context();
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"OPENPGP4FPR:1234567890123456789012345678901234567890",
|
||||
);
|
||||
assert_eq!(res.get_state(), LotState::QrFprWithoutAddr);
|
||||
assert_eq!(
|
||||
res.get_text1().unwrap(),
|
||||
"1234 5678 9012 3456 7890\n1234 5678 9012 3456 7890"
|
||||
);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
|
||||
let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890");
|
||||
assert_eq!(res.get_state(), LotState::QrError);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ fn send_handshake_msg(
|
||||
grpid: impl AsRef<str>,
|
||||
) {
|
||||
let mut msg = Message::default();
|
||||
msg.viewtype = Viewtype::Text;
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(format!("Secure-Join: {}", step));
|
||||
msg.hidden = true;
|
||||
msg.param.set_cmd(SystemMessage::SecurejoinMessage);
|
||||
@@ -341,7 +341,7 @@ impl Default for HandshakeMessageStatus {
|
||||
/// Handle incoming secure-join handshake.
|
||||
pub(crate) fn handle_securejoin_handshake(
|
||||
context: &Context,
|
||||
mimeparser: &MimeMessage,
|
||||
mimeparser: &MimeParser,
|
||||
contact_id: u32,
|
||||
) -> Result<HandshakeMessageStatus, Error> {
|
||||
let own_fingerprint: String;
|
||||
@@ -717,7 +717,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
* Tools: Misc.
|
||||
******************************************************************************/
|
||||
|
||||
fn encrypted_and_signed(mimeparser: &MimeMessage, expected_fingerprint: impl AsRef<str>) -> bool {
|
||||
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
|
||||
if !mimeparser.was_encrypted() {
|
||||
warn!(mimeparser.context, "Message not encrypted.",);
|
||||
false
|
||||
|
||||
@@ -20,23 +20,18 @@ const SMTP_TIMEOUT: u64 = 30;
|
||||
pub enum Error {
|
||||
#[fail(display = "Bad parameters")]
|
||||
BadParameters,
|
||||
|
||||
#[fail(display = "Invalid login address {}: {}", address, error)]
|
||||
InvalidLoginAddress {
|
||||
address: String,
|
||||
#[cause]
|
||||
error: error::Error,
|
||||
},
|
||||
|
||||
#[fail(display = "SMTP failed to connect: {:?}", _0)]
|
||||
ConnectionFailure(#[cause] smtp::error::Error),
|
||||
|
||||
#[fail(display = "SMTP: failed to setup connection {:?}", _0)]
|
||||
ConnectionSetupFailure(#[cause] smtp::error::Error),
|
||||
|
||||
#[fail(display = "SMTP: oauth2 error {:?}", _0)]
|
||||
Oauth2Error { address: String },
|
||||
|
||||
#[fail(display = "TLS error")]
|
||||
Tls(#[cause] native_tls::Error),
|
||||
}
|
||||
|
||||
@@ -12,12 +12,18 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub enum Error {
|
||||
#[fail(display = "Envelope error: {}", _0)]
|
||||
EnvelopeError(#[cause] async_smtp::error::Error),
|
||||
|
||||
#[fail(display = "Send error: {}", _0)]
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//!
|
||||
//! This module is only compiled for test runs.
|
||||
|
||||
use libc::uintptr_t;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::config::Config;
|
||||
@@ -30,7 +31,7 @@ pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let cb: Box<ContextCallback> = match callback {
|
||||
Some(cb) => cb,
|
||||
None => Box::new(|_, _| ()),
|
||||
None => Box::new(|_, _| 0),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
TestContext { ctx, dir }
|
||||
@@ -45,13 +46,14 @@ pub fn dummy_context() -> TestContext {
|
||||
test_context(None)
|
||||
}
|
||||
|
||||
pub fn logging_cb(_ctx: &Context, evt: Event) {
|
||||
pub fn logging_cb(_ctx: &Context, evt: Event) -> uintptr_t {
|
||||
match evt {
|
||||
Event::Info(msg) => println!("I: {}", msg),
|
||||
Event::Warning(msg) => println!("W: {}", msg),
|
||||
Event::Error(msg) => println!("E: {}", msg),
|
||||
_ => (),
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Creates Alice with a pre-generated keypair.
|
||||
|
||||
@@ -9,6 +9,6 @@ message-id: <2dfdbde7@example.org>
|
||||
Date: Sat, 14 Sep 2019 19:00:13 +0200
|
||||
From: lmn <x@tux.org>
|
||||
To: abc <abc@bcd.com>
|
||||
CC: def <Def@def.de>
|
||||
CC: def <def@def.de>
|
||||
|
||||
hi
|
||||
|
||||
@@ -203,7 +203,9 @@ fn test_encryption_decryption() {
|
||||
assert_eq!(plain, original_text);
|
||||
}
|
||||
|
||||
fn cb(_context: &Context, _event: Event) {}
|
||||
fn cb(_context: &Context, _event: Event) -> libc::uintptr_t {
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct TestContext {
|
||||
|
||||
Reference in New Issue
Block a user