mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 21:42:10 +03:00
Compare commits
6 Commits
1.0.0-beta
...
fix_html_p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dc87e9a0f | ||
|
|
c011a8cfef | ||
|
|
2d7d79fc44 | ||
|
|
119d4fa689 | ||
|
|
b7a9f73329 | ||
|
|
a52a3e0d24 |
@@ -36,6 +36,12 @@ jobs:
|
||||
executor: default
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Update submodules
|
||||
command: git submodule update --init --recursive
|
||||
- run:
|
||||
name: Calculate dependencies
|
||||
command: cargo generate-lockfile
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
@@ -43,6 +49,7 @@ jobs:
|
||||
- run: rustup default $(cat rust-toolchain)
|
||||
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
||||
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
|
||||
- run: cargo update
|
||||
- run: cargo fetch
|
||||
- run: rustc +stable --version
|
||||
- run: rustc +$(cat rust-toolchain) --version
|
||||
@@ -84,6 +91,7 @@ jobs:
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
- run: rustup install $(cat rust-toolchain)
|
||||
- run: rustup default $(cat rust-toolchain)
|
||||
- run: cargo update
|
||||
- run: cargo fetch
|
||||
- run:
|
||||
name: Test
|
||||
@@ -179,7 +187,7 @@ jobs:
|
||||
- *restore-cache
|
||||
- run:
|
||||
name: Run cargo clippy
|
||||
command: cargo clippy
|
||||
command: cargo clippy --all
|
||||
|
||||
|
||||
workflows:
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,37 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-beta.14
|
||||
|
||||
- fix packaging issue with our rust-email fork, now we are tracking
|
||||
master again there. hpk42
|
||||
|
||||
## 1.0.0-beta.13
|
||||
|
||||
- fix #976 -- unicode-issues in display-name of email addresses. @hpk42
|
||||
|
||||
- fix #985 group add/remove member bugs resulting in broken groups. @hpk42
|
||||
|
||||
- fix hanging IMAP connections -- we now detect with a 15second timeout
|
||||
if we cannot terminate the IDLE IMAP protocol. @hpk42 @link2xt
|
||||
|
||||
- fix incoming multipart/mixed containing html, to show up as
|
||||
attachments again. Fixes usage for simplebot which sends html
|
||||
files for users to interact with the bot. @adbenitez @hpk42
|
||||
|
||||
- refinements to internal autocrypt-handling code, do not send
|
||||
prefer-encrypt=nopreference as it is the default if no attribute
|
||||
is present. @linkxt
|
||||
|
||||
- simplify, modularize and rustify several parts
|
||||
of dc-core (general WIP). @link2xt @flub @hpk42 @r10s
|
||||
|
||||
- use async-email/async-smtp to handle SMTP connections, might
|
||||
fix connection/reconnection issues. @link2xt
|
||||
|
||||
- more tests and refinements for dealing with blobstorage @flub @hpk42
|
||||
|
||||
- use a dedicated build-server for CI testing of core PRs
|
||||
## next (pending)
|
||||
|
||||
- restructured (but not change) imap idle handling into own file. cc @link2xt
|
||||
|
||||
## 1.0.0-beta.12
|
||||
|
||||
|
||||
85
Cargo.lock
generated
85
Cargo.lock
generated
@@ -116,31 +116,6 @@ dependencies = [
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-smtp"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/async-email/async-smtp#b2c9e8a1a817575d3911152103692ec3d033fd96"
|
||||
dependencies = [
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-tls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-trait 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustls-connector 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webpki-roots 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-std"
|
||||
version = "1.2.0"
|
||||
@@ -186,16 +161,6 @@ dependencies = [
|
||||
"webpki-roots 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.13"
|
||||
@@ -642,10 +607,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.13"
|
||||
version = "1.0.0-beta.12"
|
||||
dependencies = [
|
||||
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap)",
|
||||
"async-smtp 0.1.0 (git+https://github.com/async-email/async-smtp)",
|
||||
"async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"async-tls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -656,16 +620,15 @@ dependencies = [
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deltachat_derive 0.1.0",
|
||||
"email 0.0.21 (git+https://github.com/deltachat/rust-email)",
|
||||
"encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)",
|
||||
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lettre 0.9.2 (git+https://github.com/deltachat/lettre?branch=feat/mail)",
|
||||
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre?branch=feat/mail)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mailparse 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -720,9 +683,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.13"
|
||||
version = "1.0.0-beta.12"
|
||||
dependencies = [
|
||||
"deltachat 1.0.0-beta.13",
|
||||
"deltachat 1.0.0-beta.12",
|
||||
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -797,11 +760,6 @@ dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.4"
|
||||
@@ -828,11 +786,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
[[package]]
|
||||
name = "email"
|
||||
version = "0.0.21"
|
||||
source = "git+https://github.com/deltachat/rust-email#38b325ea23bf9e71da7b5bc1b70d986d09a9e5a7"
|
||||
source = "git+https://github.com/deltachat/rust-email#265a54a8c31355c506610c032c81112dc0953afb"
|
||||
dependencies = [
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)",
|
||||
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1458,9 +1415,17 @@ name = "lettre"
|
||||
version = "0.9.2"
|
||||
source = "git+https://github.com/deltachat/lettre?branch=feat/mail#00ba9db544059ddd60048f0b85d5052e4bf605da"
|
||||
dependencies = [
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustls-connector 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webpki-roots 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2642,25 +2607,6 @@ name = "smallvec"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"snafu-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sourcefile"
|
||||
version = "0.1.4"
|
||||
@@ -3447,11 +3393,9 @@ dependencies = [
|
||||
"checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
|
||||
"checksum async-imap 0.1.1 (git+https://github.com/async-email/async-imap)" = "<none>"
|
||||
"checksum async-macros 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "644a5a8de80f2085a1e7e57cd1544a2a7438f6e003c0790999bd43b92a77cdb2"
|
||||
"checksum async-smtp 0.1.0 (git+https://github.com/async-email/async-smtp)" = "<none>"
|
||||
"checksum async-std 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "513ee3c49800679a319912340f5601afda9e72848d7dea3a48bab489e8c1a46f"
|
||||
"checksum async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de6bd58f7b9cc49032559422595c81cbfcf04db2f2133592f70af19e258a1ced"
|
||||
"checksum async-tls 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce6977f57fa68da77ffe5542950d47e9c23d65f5bc7cb0a9f8700996913eec7"
|
||||
"checksum async-trait 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "8b6dd385bb33043b833ba049048d57bdbb4d654a121ed68c71871ca51ff67070"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
|
||||
@@ -3513,7 +3457,6 @@ dependencies = [
|
||||
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
|
||||
"checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97"
|
||||
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
|
||||
"checksum ed25519-dalek 1.0.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)" = "845aaacc16f01178f33349e7c992ecd0cee095aa5e577f0f4dee35971bd36455"
|
||||
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
@@ -3714,8 +3657,6 @@ dependencies = [
|
||||
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"
|
||||
"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
|
||||
"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86"
|
||||
"checksum snafu 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41207ca11f96a62cd34e6b7fdf73d322b25ae3848eb9d38302169724bb32cf27"
|
||||
"checksum snafu-derive 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5e338c8b0577457c9dda8e794b6ad7231c96e25b1b0dd5842d52249020c1c0"
|
||||
"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
|
||||
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-beta.14"
|
||||
version = "1.0.0-beta.12"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
@@ -17,8 +17,7 @@ smallvec = "0.6.9"
|
||||
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
|
||||
num-derive = "0.2.5"
|
||||
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 = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", branch="master" }
|
||||
async-tls = "0.6"
|
||||
@@ -31,7 +30,6 @@ serde_json = "1.0"
|
||||
chrono = "0.4.6"
|
||||
failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
indexmap = "1.3.0"
|
||||
# TODO: make optional
|
||||
rustyline = "4.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ install:
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- cargo update
|
||||
|
||||
build: false
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-beta.14"
|
||||
version = "1.0.0-beta.12"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -4050,6 +4050,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_CERTCK_STRICT 1
|
||||
|
||||
/**
|
||||
* Accept invalid hostnames, but not invalid certificates.
|
||||
*/
|
||||
#define DC_CERTCK_ACCEPT_INVALID_HOSTNAMES 2
|
||||
|
||||
/**
|
||||
* Accept invalid certificates, including self-signed ones
|
||||
* or having incorrect hostname.
|
||||
|
||||
@@ -29,8 +29,6 @@ use deltachat::message::MsgId;
|
||||
use deltachat::stock::StockMessage;
|
||||
use deltachat::*;
|
||||
|
||||
mod dc_array;
|
||||
|
||||
mod string;
|
||||
use self::string::*;
|
||||
|
||||
|
||||
@@ -94,9 +94,7 @@ pub fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
|
||||
let data = dc_read_file(context, filename)?;
|
||||
|
||||
if let Err(err) = dc_receive_imf(context, &data, "import", 0, 0) {
|
||||
println!("dc_receive_imf errored: {:?}", err);
|
||||
}
|
||||
dc_receive_imf(context, &data, "import", 0, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -109,30 +109,6 @@ class Chat(object):
|
||||
|
||||
# ------ chat messaging API ------------------------------
|
||||
|
||||
def send_msg(self, msg):
|
||||
"""send a message by using a ready Message object.
|
||||
|
||||
:param msg: a :class:`deltachat.message.Message` instance
|
||||
previously returned by
|
||||
e.g. :meth:`deltachat.message.Message.new_empty` or
|
||||
:meth:`prepare_file`.
|
||||
:raises ValueError: if message can not be sent.
|
||||
|
||||
:returns: a :class:`deltachat.message.Message` instance as
|
||||
sent out. This is the same object as was passed in, which
|
||||
has been modified with the new state of the core.
|
||||
"""
|
||||
if msg.is_out_preparing():
|
||||
assert msg.id != 0
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
msg = Message.from_db(self.account, msg.id)
|
||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
# modify message in place to avoid bad state for the caller
|
||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
||||
return msg
|
||||
|
||||
def send_text(self, text):
|
||||
""" send a text message and return the resulting Message instance.
|
||||
|
||||
@@ -154,12 +130,9 @@ class Chat(object):
|
||||
:raises ValueError: if message can not be send/chat does not exist.
|
||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||
"""
|
||||
msg = Message.new_empty(self.account, view_type="file")
|
||||
msg.set_file(path, mime_type)
|
||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
return Message.from_db(self.account, sent_id)
|
||||
msg = self.prepare_message_file(path=path, mime_type=mime_type)
|
||||
self.send_prepared(msg)
|
||||
return msg
|
||||
|
||||
def send_image(self, path):
|
||||
""" send an image message and return the resulting Message instance.
|
||||
@@ -169,12 +142,9 @@ class Chat(object):
|
||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||
"""
|
||||
mime_type = mimetypes.guess_type(path)[0]
|
||||
msg = Message.new_empty(self.account, view_type="image")
|
||||
msg.set_file(path, mime_type)
|
||||
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
return Message.from_db(self.account, sent_id)
|
||||
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
|
||||
self.send_prepared(msg)
|
||||
return msg
|
||||
|
||||
def prepare_message(self, msg):
|
||||
""" create a new prepared message.
|
||||
|
||||
@@ -68,6 +68,7 @@ DC_LP_SMTP_SOCKET_SSL = 0x20000
|
||||
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||
DC_CERTCK_AUTO = 0
|
||||
DC_CERTCK_STRICT = 1
|
||||
DC_CERTCK_ACCEPT_INVALID_HOSTNAMES = 2
|
||||
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3
|
||||
DC_EMPTY_MVBOX = 0x01
|
||||
DC_EMPTY_INBOX = 0x02
|
||||
|
||||
@@ -174,7 +174,7 @@ class Message(object):
|
||||
@property
|
||||
def _msgstate(self):
|
||||
if self.id == 0:
|
||||
dc_msg = self._dc_msg
|
||||
dc_msg = self.message._dc_msg
|
||||
else:
|
||||
# load message from db to get a fresh/current state
|
||||
dc_msg = ffi.gc(
|
||||
|
||||
@@ -85,21 +85,16 @@ class SessionLiveConfigFromFile:
|
||||
class SessionLiveConfigFromURL:
|
||||
def __init__(self, url, create_token):
|
||||
self.configlist = []
|
||||
self.url = url
|
||||
self.create_token = create_token
|
||||
|
||||
def get(self, index):
|
||||
try:
|
||||
return self.configlist[index]
|
||||
except IndexError:
|
||||
assert index == len(self.configlist), index
|
||||
res = requests.post(self.url, json={"token_create_user": int(self.create_token)})
|
||||
for i in range(2):
|
||||
res = requests.post(url, json={"token_create_user": int(create_token)})
|
||||
if res.status_code != 200:
|
||||
pytest.skip("creating newtmpuser failed {!r}".format(res))
|
||||
d = res.json()
|
||||
config = dict(addr=d["email"], mail_pw=d["password"])
|
||||
self.configlist.append(config)
|
||||
return config
|
||||
|
||||
def get(self, index):
|
||||
return self.configlist[index]
|
||||
|
||||
def exists(self):
|
||||
return bool(self.configlist)
|
||||
|
||||
@@ -382,23 +382,6 @@ class TestOfflineChat:
|
||||
assert not res.is_ask_verifygroup()
|
||||
assert res.contact_id == 10
|
||||
|
||||
def test_group_chat_many_members_add_remove(self, ac1, lp):
|
||||
lp.sec("ac1: creating group chat with 10 other members")
|
||||
chat = ac1.create_group_chat(name="title1")
|
||||
contacts = []
|
||||
for i in range(10):
|
||||
contact = ac1.create_contact("some{}@example.org".format(i))
|
||||
contacts.append(contact)
|
||||
chat.add_contact(contact)
|
||||
|
||||
num_contacts = len(chat.get_contacts())
|
||||
assert num_contacts == 11
|
||||
|
||||
lp.sec("ac1: removing two contacts and checking things are right")
|
||||
chat.remove_contact(contacts[9])
|
||||
chat.remove_contact(contacts[3])
|
||||
assert len(chat.get_contacts()) == 9
|
||||
|
||||
|
||||
class TestOnlineAccount:
|
||||
def get_chat(self, ac1, ac2, both_created=False):
|
||||
@@ -473,7 +456,10 @@ class TestOnlineAccount:
|
||||
msg1 = Message.new_empty(ac1, "file")
|
||||
msg1.set_text("withfile")
|
||||
msg1.set_file(p)
|
||||
chat.send_msg(msg1)
|
||||
message = chat.prepare_message(msg1)
|
||||
assert message.is_out_preparing()
|
||||
assert message.text == "withfile"
|
||||
chat.send_prepared(message)
|
||||
|
||||
lp.sec("ac2: receive message")
|
||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
@@ -632,9 +618,6 @@ class TestOnlineAccount:
|
||||
def test_send_and_receive_message_markseen(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
# make DC's life harder wrt to encodings
|
||||
ac1.set_config("displayname", "ä name")
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = self.get_chat(ac1, ac2)
|
||||
|
||||
@@ -652,7 +635,6 @@ class TestOnlineAccount:
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
assert msg_in.text == "message1"
|
||||
assert not msg_in.is_forwarded()
|
||||
assert msg_in.get_sender_contact().display_name == ac1.get_config("displayname")
|
||||
|
||||
lp.sec("check the message arrived in contact-requets/deaddrop")
|
||||
chat2 = msg_in.chat
|
||||
@@ -1078,52 +1060,6 @@ class TestOnlineAccount:
|
||||
assert not locations3
|
||||
|
||||
|
||||
class TestGroupStressTests:
|
||||
def test_group_many_members_add_leave_remove(self, acfactory, lp):
|
||||
lp.sec("creating and configuring five accounts")
|
||||
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)
|
||||
|
||||
lp.sec("ac1: creating group chat with 3 other members")
|
||||
chat = ac1.create_group_chat("title1")
|
||||
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")
|
||||
chat.send_text("hello")
|
||||
assert chat.is_promoted()
|
||||
|
||||
num_contacts = len(chat.get_contacts())
|
||||
assert num_contacts == 3 + 1
|
||||
|
||||
lp.sec("ac2: checking that the chat arrived correctly")
|
||||
ac2 = accounts[0]
|
||||
msg = ac2.wait_next_incoming_message()
|
||||
assert msg.text == "hello"
|
||||
print("chat is", msg.chat)
|
||||
assert len(msg.chat.get_contacts()) == 4
|
||||
|
||||
lp.sec("ac1: removing one contacts and checking things are right")
|
||||
to_remove = msg.chat.get_contacts()[-1]
|
||||
msg.chat.remove_contact(to_remove)
|
||||
|
||||
sysmsg = ac1.wait_next_incoming_message()
|
||||
assert to_remove.addr in sysmsg.text
|
||||
assert len(sysmsg.chat.get_contacts()) == 3
|
||||
|
||||
|
||||
class TestOnlineConfigureFails:
|
||||
def test_invalid_password(self, acfactory):
|
||||
ac1, configdict = acfactory.get_online_config()
|
||||
|
||||
@@ -1,49 +1,10 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
from filecmp import cmp
|
||||
|
||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||
from deltachat import const
|
||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||
|
||||
|
||||
class TestOnlineInCreation:
|
||||
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
|
||||
lp.sec("Creating in-creation file outside of blobdir")
|
||||
assert tmpdir.strpath != ac1.get_blobdir()
|
||||
src = tmpdir.join('file.txt').ensure(file=1)
|
||||
with pytest.raises(Exception):
|
||||
chat.prepare_message_file(src.strpath)
|
||||
|
||||
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
wait_configuration_progress(ac1, 1000)
|
||||
wait_configuration_progress(ac2, 1000)
|
||||
|
||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
|
||||
lp.sec("Creating file outside of blobdir")
|
||||
assert tmpdir.strpath != ac1.get_blobdir()
|
||||
src = tmpdir.join('file.txt')
|
||||
src.write("hello there\n")
|
||||
chat.send_file(src.strpath)
|
||||
|
||||
blob_src = os.path.join(ac1.get_blobdir(), 'file.txt')
|
||||
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
|
||||
|
||||
def test_forward_increation(self, acfactory, data, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.get_online_configuring_account()
|
||||
@@ -56,10 +17,7 @@ class TestOnlineInCreation:
|
||||
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
||||
|
||||
lp.sec("create a message with a file in creation")
|
||||
orig = data.get_path("d.png")
|
||||
path = os.path.join(ac1.get_blobdir(), 'd.png')
|
||||
with open(path, "x") as fp:
|
||||
fp.write("preparing")
|
||||
path = data.get_path("d.png")
|
||||
prepared_original = chat.prepare_message_file(path)
|
||||
assert prepared_original.is_out_preparing()
|
||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||
@@ -80,7 +38,6 @@ class TestOnlineInCreation:
|
||||
|
||||
lp.sec("finish creating the file and send it")
|
||||
assert prepared_original.is_out_preparing()
|
||||
shutil.copyfile(orig, path)
|
||||
chat.send_prepared(prepared_original)
|
||||
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
|
||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||
@@ -102,11 +59,11 @@ class TestOnlineInCreation:
|
||||
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
received_original = ac2.get_message_by_id(ev1[2])
|
||||
assert cmp(received_original.filename, orig, shallow=False)
|
||||
assert cmp(received_original.filename, path, False)
|
||||
|
||||
lp.sec("wait2 for original or forwarded messages to arrive")
|
||||
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
assert ev2[1] != ev1[1]
|
||||
received_copy = ac2.get_message_by_id(ev2[2])
|
||||
assert cmp(received_copy.filename, orig, shallow=False)
|
||||
assert cmp(received_copy.filename, path, False)
|
||||
|
||||
@@ -65,8 +65,8 @@ commands =
|
||||
|
||||
|
||||
[pytest]
|
||||
addopts = -v -ra
|
||||
python_files = tests/test_*.py
|
||||
addopts = -v -rs
|
||||
python_files = tests/test_*.py
|
||||
norecursedirs = .tox
|
||||
xfail_strict=true
|
||||
timeout = 60
|
||||
|
||||
@@ -55,7 +55,7 @@ if __name__ == "__main__":
|
||||
replace_toml_version("Cargo.toml", newversion)
|
||||
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
|
||||
|
||||
# subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||
subprocess.call(["cargo", "update", "-p", "deltachat"])
|
||||
|
||||
print("after commit make sure to: ")
|
||||
print("")
|
||||
|
||||
@@ -159,7 +159,7 @@ impl<'a> BlobObject<'a> {
|
||||
/// This merely delegates to the [BlobObject::create_and_copy] and
|
||||
/// the [BlobObject::from_path] methods. See those for possible
|
||||
/// errors.
|
||||
pub fn new_from_path(
|
||||
pub fn create_from_path(
|
||||
context: &Context,
|
||||
src: impl AsRef<Path>,
|
||||
) -> std::result::Result<BlobObject, BlobError> {
|
||||
@@ -559,14 +559,14 @@ mod tests {
|
||||
|
||||
let src_ext = t.dir.path().join("external");
|
||||
fs::write(&src_ext, b"boo").unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
||||
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
|
||||
assert_eq!(blob.as_name(), "$BLOBDIR/external");
|
||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||
assert_eq!(data, b"boo");
|
||||
|
||||
let src_int = t.ctx.get_blobdir().join("internal");
|
||||
fs::write(&src_int, b"boo").unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap();
|
||||
let blob = BlobObject::create_from_path(&t.ctx, &src_int).unwrap();
|
||||
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
|
||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||
assert_eq!(data, b"boo");
|
||||
@@ -576,7 +576,7 @@ mod tests {
|
||||
let t = dummy_context();
|
||||
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
|
||||
fs::write(&src_ext, b"boo").unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
||||
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap();
|
||||
assert_eq!(
|
||||
blob.as_name(),
|
||||
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
|
||||
|
||||
40
src/chat.rs
40
src/chat.rs
@@ -725,11 +725,20 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||
if msg.type_0 == Viewtype::Text {
|
||||
// the caller should check if the message text is empty
|
||||
} 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.type_0))?;
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
let blob = if let Some(f) = msg.param.get_file(Param::File, context)? {
|
||||
match f {
|
||||
ParamsFile::Blob(blob) => blob,
|
||||
ParamsFile::FsPath(path) => {
|
||||
// path is outside the blobdir, let's copy
|
||||
let blob = BlobObject::create_and_copy(context, path)?;
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
|
||||
blob
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bail!("Attachment missing for message of type #{}", msg.type_0);
|
||||
};
|
||||
|
||||
if msg.type_0 == Viewtype::File || msg.type_0 == Viewtype::Image {
|
||||
// Correct the type, take care not to correct already very special
|
||||
@@ -2403,25 +2412,4 @@ mod tests {
|
||||
"bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_same_chat_twice() {
|
||||
let context = dummy_context();
|
||||
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(contact1, 0);
|
||||
|
||||
let chat_id = create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert!(
|
||||
chat_id > DC_CHAT_ID_LAST_SPECIAL,
|
||||
"chat_id too small {}",
|
||||
chat_id
|
||||
);
|
||||
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
|
||||
|
||||
let chat2_id = create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert_eq!(chat2_id, chat_id);
|
||||
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
|
||||
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ impl Context {
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
|
||||
match key {
|
||||
Config::Selfavatar if value.is_some() => {
|
||||
let blob = BlobObject::new_from_path(&self, value.unwrap())?;
|
||||
let blob = BlobObject::create_from_path(&self, value.unwrap())?;
|
||||
self.sql.set_raw_config(self, key, Some(blob.as_name()))
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
|
||||
@@ -485,12 +485,8 @@ fn try_imap_connection(
|
||||
|
||||
fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
|
||||
let inf = format!(
|
||||
"imap: {}@{}:{} flags=0x{:x} certificate_checks={}",
|
||||
param.mail_user,
|
||||
param.mail_server,
|
||||
param.mail_port,
|
||||
param.server_flags,
|
||||
param.imap_certificate_checks
|
||||
"imap: {}@{}:{} flags=0x{:x}",
|
||||
param.mail_user, param.mail_server, param.mail_port, param.server_flags
|
||||
);
|
||||
info!(context, "Trying: {}", inf);
|
||||
if context
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::Result;
|
||||
use crate::events::Event;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
@@ -861,14 +861,14 @@ impl Contact {
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
|
||||
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut bool) -> Origin {
|
||||
pub fn get_origin_by_id(context: &Context, contact_id: u32, ret_blocked: &mut i32) -> Origin {
|
||||
let mut ret = Origin::Unknown;
|
||||
*ret_blocked = false;
|
||||
*ret_blocked = 0;
|
||||
|
||||
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;
|
||||
*ret_blocked = 1;
|
||||
} else {
|
||||
ret = contact.origin;
|
||||
}
|
||||
@@ -1021,18 +1021,6 @@ fn cat_fingerprint(
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// determine whether the specified addr maps to the/a self addr
|
||||
pub fn is_self_addr(&self, addr: &str) -> Result<bool> {
|
||||
let self_addr = match self.get_config(Config::ConfiguredAddr) {
|
||||
Some(s) => s,
|
||||
None => return Err(Error::NotConfigured),
|
||||
};
|
||||
|
||||
Ok(addr_cmp(self_addr, addr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
||||
let norm1 = addr_normalize(addr1.as_ref()).to_lowercase();
|
||||
let norm2 = addr_normalize(addr2.as_ref()).to_lowercase();
|
||||
@@ -1040,6 +1028,15 @@ pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
||||
norm1 == norm2
|
||||
}
|
||||
|
||||
pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
|
||||
if !addr.as_ref().is_empty() {
|
||||
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
|
||||
return addr_cmp(addr, self_addr);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn split_address_book(book: &str) -> Vec<(&str, &str)> {
|
||||
book.lines()
|
||||
.chunks(2)
|
||||
@@ -1123,18 +1120,6 @@ mod tests {
|
||||
assert_eq!(contacts.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_self_addr() -> Result<()> {
|
||||
let t = test_context(None);
|
||||
assert!(t.ctx.is_self_addr("me@me.org").is_err());
|
||||
|
||||
let addr = configure_alice_keypair(&t.ctx);
|
||||
assert_eq!(t.ctx.is_self_addr("me@me.org")?, false);
|
||||
assert_eq!(t.ctx.is_self_addr(&addr)?, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_or_lookup() {
|
||||
// add some contacts, this also tests add_address_book()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -182,6 +182,22 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
|
||||
String::from_utf8(wrapped_writer).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn dc_create_incoming_rfc724_mid(
|
||||
message_timestamp: i64,
|
||||
contact_id_from: u32,
|
||||
contact_ids_to: &[u32],
|
||||
) -> 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();
|
||||
let result = format!(
|
||||
"{}-{}-{}@stub",
|
||||
message_timestamp, contact_id_from, largest_id_to
|
||||
);
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Function generates a Message-ID that can be used for a new outgoing message.
|
||||
/// - this function is called for all outgoing messages.
|
||||
/// - the message ID should be globally unique
|
||||
@@ -764,6 +780,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_create_incoming_rfc724_mid() {
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &[6, 7]);
|
||||
assert_eq!(res, Some("123-45-7@stub".into()));
|
||||
let res = dc_create_incoming_rfc724_mid(123, 45, &[]);
|
||||
assert_eq!(res, Some("123-45-0@stub".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_get_safe_basename() {
|
||||
assert_eq!(get_safe_basename("12312/hello"), "hello");
|
||||
|
||||
@@ -28,14 +28,12 @@ pub enum Error {
|
||||
InvalidMsgId,
|
||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
||||
WatchFolderNotFound(String),
|
||||
#[fail(display = "Invalid Email: {:?}", _0)]
|
||||
#[fail(display = "Inalid Email: {:?}", _0)]
|
||||
MailParseError(#[cause] mailparse::MailParseError),
|
||||
#[fail(display = "Building invalid Email: {:?}", _0)]
|
||||
LettreError(#[cause] lettre_email::error::Error),
|
||||
#[fail(display = "FromStr error: {:?}", _0)]
|
||||
FromStr(#[cause] mime::FromStrError),
|
||||
#[fail(display = "Not Configured")]
|
||||
NotConfigured,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
#[allow(dead_code)]
|
||||
pub enum HeaderDef {
|
||||
MessageId,
|
||||
Subject,
|
||||
Date,
|
||||
From_,
|
||||
To,
|
||||
Cc,
|
||||
Disposition,
|
||||
OriginalMessageId,
|
||||
ListId,
|
||||
References,
|
||||
InReplyTo,
|
||||
Precedence,
|
||||
ChatVersion,
|
||||
ChatGroupId,
|
||||
ChatGroupName,
|
||||
ChatGroupNameChanged,
|
||||
ChatVerified,
|
||||
ChatGroupImage,
|
||||
ChatVoiceMessage,
|
||||
ChatGroupMemberRemoved,
|
||||
ChatGroupMemberAdded,
|
||||
ChatContent,
|
||||
ChatDuration,
|
||||
ChatDispositionNotificationTo,
|
||||
AutocryptSetupMessage,
|
||||
SecureJoin,
|
||||
SecureJoinGroup,
|
||||
SecureJoinFingerprint,
|
||||
SecureJoinInvitenumber,
|
||||
SecureJoinAuth,
|
||||
_TestHeader,
|
||||
}
|
||||
|
||||
impl HeaderDef {
|
||||
/// Returns the corresponding Event id.
|
||||
pub fn get_headername(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
/// Test that kebab_case serialization works as expected
|
||||
fn kebab_test() {
|
||||
assert_eq!(HeaderDef::From_.to_string(), "from");
|
||||
|
||||
assert_eq!(HeaderDef::_TestHeader.to_string(), "test-header");
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,6 @@ pub enum Error {
|
||||
#[fail(display = "IMAP IDLE protocol failed to init/complete")]
|
||||
IdleProtocolFailed(#[cause] async_imap::error::Error),
|
||||
|
||||
#[fail(display = "IMAP IDLE protocol timed out")]
|
||||
IdleTimeout(#[cause] async_std::future::TimeoutError),
|
||||
|
||||
#[fail(display = "IMAP server does not have IDLE capability")]
|
||||
IdleAbilityMissing,
|
||||
|
||||
@@ -94,17 +91,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we can't properly terminate the idle
|
||||
// protocol let's break the connection.
|
||||
let res =
|
||||
async_std::future::timeout(Duration::from_secs(15), handle.done())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
self.trigger_reconnect();
|
||||
Error::IdleTimeout(err)
|
||||
})?;
|
||||
|
||||
match res {
|
||||
match handle.done().await {
|
||||
Ok(session) => {
|
||||
*self.session.lock().await = Some(Session::Secure(session));
|
||||
}
|
||||
@@ -148,17 +135,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we can't properly terminate the idle
|
||||
// protocol let's break the connection.
|
||||
let res =
|
||||
async_std::future::timeout(Duration::from_secs(15), handle.done())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
self.trigger_reconnect();
|
||||
Error::IdleTimeout(err)
|
||||
})?;
|
||||
|
||||
match res {
|
||||
match handle.done().await {
|
||||
Ok(session) => {
|
||||
*self.session.lock().await = Some(Session::Insecure(session));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use async_imap::{
|
||||
error::Result as ImapResult,
|
||||
types::{Capability, Fetch, Flag, Mailbox, Name, NameAttribute},
|
||||
types::{Fetch, Flag, Mailbox, Name, NameAttribute},
|
||||
};
|
||||
use async_std::sync::{Mutex, RwLock};
|
||||
use async_std::task;
|
||||
@@ -389,14 +389,9 @@ impl Imap {
|
||||
} else {
|
||||
let can_idle = caps.has_str("IDLE");
|
||||
let has_xlist = caps.has_str("XLIST");
|
||||
let caps_list = caps.iter().fold(String::new(), |s, c| {
|
||||
if let Capability::Atom(x) = c {
|
||||
s + &format!(" {}", x)
|
||||
} else {
|
||||
s + &format!(" {:?}", c)
|
||||
}
|
||||
});
|
||||
|
||||
let caps_list = caps
|
||||
.iter()
|
||||
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
|
||||
self.config.write().await.can_idle = can_idle;
|
||||
self.config.write().await.has_xlist = has_xlist;
|
||||
*self.connected.lock().await = true;
|
||||
@@ -718,17 +713,7 @@ impl Imap {
|
||||
|
||||
if !is_deleted && msg.body().is_some() {
|
||||
let body = msg.body().unwrap_or_default();
|
||||
if let Err(err) =
|
||||
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32)
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"dc_receive_imf failed for imap-message {}/{}: {:?}",
|
||||
folder.as_ref(),
|
||||
server_uid,
|
||||
err
|
||||
);
|
||||
}
|
||||
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
src/job.rs
20
src/job.rs
@@ -167,15 +167,13 @@ impl Job {
|
||||
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
|
||||
}
|
||||
},
|
||||
)
|
||||
.filter_map(|addr| match lettre::EmailAddress::new(addr.to_string()) {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
warn!(context, "invalid recipient: {} {:?}", addr, err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||
@@ -200,11 +198,11 @@ impl Job {
|
||||
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)) {
|
||||
match smtp.send(context, recipients_list, body, self.job_id) {
|
||||
Err(crate::smtp::send::Error::SendError(err)) => {
|
||||
// Remote error, retry later.
|
||||
info!(context, "SMTP failed to send: {}", err);
|
||||
smtp.disconnect();
|
||||
info!(context, "SMTP failed to send: {}", err);
|
||||
self.try_again_later(TryAgain::AtOnce, Some(err.to_string()));
|
||||
}
|
||||
Err(crate::smtp::send::Error::EnvelopeError(err)) => {
|
||||
|
||||
@@ -33,8 +33,6 @@ mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
pub mod headerdef;
|
||||
|
||||
pub(crate) mod events;
|
||||
pub use events::*;
|
||||
|
||||
@@ -75,6 +73,7 @@ mod token;
|
||||
mod wrapmime;
|
||||
mod dehtml;
|
||||
|
||||
pub mod dc_array;
|
||||
pub mod dc_receive_imf;
|
||||
mod dc_simplify;
|
||||
pub mod dc_tools;
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use async_std::sync::Arc;
|
||||
use rustls;
|
||||
use webpki;
|
||||
@@ -15,11 +16,7 @@ use webpki_roots;
|
||||
pub enum CertificateChecks {
|
||||
Automatic = 0,
|
||||
Strict = 1,
|
||||
|
||||
/// Same as AcceptInvalidCertificates
|
||||
/// Previously known as AcceptInvalidHostnames, now deprecated.
|
||||
AcceptInvalidCertificates2 = 2,
|
||||
|
||||
AcceptInvalidHostnames = 2,
|
||||
AcceptInvalidCertificates = 3,
|
||||
}
|
||||
|
||||
@@ -133,7 +130,7 @@ impl LoginParam {
|
||||
&self,
|
||||
context: &Context,
|
||||
prefix: impl AsRef<str>,
|
||||
) -> crate::sql::Result<()> {
|
||||
) -> Result<(), Error> {
|
||||
let prefix = prefix.as_ref();
|
||||
let sql = &context.sql;
|
||||
|
||||
@@ -291,8 +288,14 @@ pub fn dc_build_tls_config(certificate_checks: CertificateChecks) -> rustls::Cli
|
||||
.dangerous()
|
||||
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
|
||||
}
|
||||
CertificateChecks::AcceptInvalidCertificates
|
||||
| CertificateChecks::AcceptInvalidCertificates2 => {
|
||||
CertificateChecks::AcceptInvalidCertificates => {
|
||||
// TODO: only accept invalid certs
|
||||
config
|
||||
.dangerous()
|
||||
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
|
||||
}
|
||||
CertificateChecks::AcceptInvalidHostnames => {
|
||||
// TODO: only accept invalid hostnames
|
||||
config
|
||||
.dangerous()
|
||||
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
|
||||
@@ -310,8 +313,8 @@ mod tests {
|
||||
use std::string::ToString;
|
||||
|
||||
assert_eq!(
|
||||
"accept_invalid_certificates".to_string(),
|
||||
CertificateChecks::AcceptInvalidCertificates.to_string()
|
||||
"accept_invalid_hostnames".to_string(),
|
||||
CertificateChecks::AcceptInvalidHostnames.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let mut unprotected_headers: Vec<Header> = Vec::new();
|
||||
|
||||
let from = Address::new_mailbox_with_name(
|
||||
self.from_displayname.to_string(),
|
||||
encode_words(&self.from_displayname),
|
||||
self.from_addr.clone(),
|
||||
);
|
||||
|
||||
@@ -394,7 +394,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
to.push(Address::new_mailbox(addr.clone()));
|
||||
} else {
|
||||
to.push(Address::new_mailbox_with_name(
|
||||
name.to_string(),
|
||||
encode_words(name),
|
||||
addr.clone(),
|
||||
));
|
||||
}
|
||||
@@ -1051,25 +1051,3 @@ pub fn needs_encoding(to_check: impl AsRef<str>) -> bool {
|
||||
!c.is_ascii_alphanumeric() && c != '-' && c != '_' && c != '.' && c != '~' && c != '%'
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_render_email_address() {
|
||||
let display_name = "ä space";
|
||||
let addr = "x@y.org";
|
||||
|
||||
assert!(!display_name.is_ascii());
|
||||
|
||||
let s = format!(
|
||||
"{}",
|
||||
Address::new_mailbox_with_name(display_name.to_string(), addr.to_string())
|
||||
);
|
||||
|
||||
println!("{}", s);
|
||||
|
||||
assert_eq!(s, "=?utf-8?q?=C3=A4_space?= <x@y.org>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::dc_simplify::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::location;
|
||||
use crate::message;
|
||||
@@ -29,8 +28,10 @@ use crate::{bail, ensure};
|
||||
pub struct MimeParser<'a> {
|
||||
pub context: &'a Context,
|
||||
pub parts: Vec<Part>,
|
||||
header: HashMap<String, String>,
|
||||
pub header: HashMap<String, String>,
|
||||
pub subject: Option<String>,
|
||||
pub decrypting_failed: bool,
|
||||
pub encrypted: bool,
|
||||
pub signatures: HashSet<String>,
|
||||
pub gossipped_addr: HashSet<String>,
|
||||
pub is_forwarded: bool,
|
||||
@@ -72,9 +73,9 @@ impl<'a> MimeParser<'a> {
|
||||
let mut parser = MimeParser {
|
||||
parts: Vec::new(),
|
||||
header: Default::default(),
|
||||
subject: None,
|
||||
decrypting_failed: false,
|
||||
|
||||
// only non-empty if it was a valid autocrypt message
|
||||
encrypted: false,
|
||||
signatures: Default::default(),
|
||||
gossipped_addr: Default::default(),
|
||||
is_forwarded: false,
|
||||
@@ -101,6 +102,7 @@ impl<'a> MimeParser<'a> {
|
||||
let mail = match e2ee::try_decrypt(parser.context, &mail, message_time) {
|
||||
Ok((raw, signatures)) => {
|
||||
// Valid autocrypt message, encrypted
|
||||
parser.encrypted = raw.is_some();
|
||||
parser.signatures = signatures;
|
||||
|
||||
if let Some(raw) = raw {
|
||||
@@ -145,7 +147,11 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
|
||||
fn parse_headers(&mut self) -> Result<()> {
|
||||
if self.get(HeaderDef::AutocryptSetupMessage).is_some() {
|
||||
if let Some(field) = self.lookup_field("Subject") {
|
||||
self.subject = Some(field.clone());
|
||||
}
|
||||
|
||||
if self.lookup_field("Autocrypt-Setup-Message").is_some() {
|
||||
let has_setup_file = self.parts.iter().any(|p| {
|
||||
p.mimetype.is_some() && p.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
|
||||
});
|
||||
@@ -176,12 +182,12 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(value) = self.get(HeaderDef::ChatContent) {
|
||||
} else if let Some(value) = self.lookup_field("Chat-Content") {
|
||||
if value == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
}
|
||||
}
|
||||
if self.get(HeaderDef::ChatGroupImage).is_some() && !self.parts.is_empty() {
|
||||
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
|
||||
let textpart = &self.parts[0];
|
||||
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
|
||||
let imgpart = &mut self.parts[1];
|
||||
@@ -219,7 +225,7 @@ impl<'a> MimeParser<'a> {
|
||||
std::mem::replace(&mut self.parts[0], filepart);
|
||||
}
|
||||
}
|
||||
if let Some(ref subject) = self.get_subject() {
|
||||
if let Some(ref subject) = self.subject {
|
||||
let mut prepend_subject = 1i32;
|
||||
if !self.decrypting_failed {
|
||||
let colon = subject.find(':');
|
||||
@@ -256,13 +262,13 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
if self.parts.len() == 1 {
|
||||
if self.parts[0].typ == Viewtype::Audio
|
||||
&& self.get(HeaderDef::ChatVoiceMessage).is_some()
|
||||
&& self.lookup_field("Chat-Voice-Message").is_some()
|
||||
{
|
||||
let part_mut = &mut self.parts[0];
|
||||
part_mut.typ = Viewtype::Voice;
|
||||
}
|
||||
if self.parts[0].typ == Viewtype::Image {
|
||||
if let Some(value) = self.get(HeaderDef::ChatContent) {
|
||||
if let Some(value) = self.lookup_field("Chat-Content") {
|
||||
if value == "sticker" {
|
||||
let part_mut = &mut self.parts[0];
|
||||
part_mut.typ = Viewtype::Sticker;
|
||||
@@ -274,7 +280,7 @@ impl<'a> MimeParser<'a> {
|
||||
|| part.typ == Viewtype::Voice
|
||||
|| part.typ == Viewtype::Video
|
||||
{
|
||||
if let Some(field_0) = self.get(HeaderDef::ChatDuration) {
|
||||
if let Some(field_0) = self.lookup_field("Chat-Duration") {
|
||||
let duration_ms = field_0.parse().unwrap_or_default();
|
||||
if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
|
||||
let part_mut = &mut self.parts[0];
|
||||
@@ -284,12 +290,12 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
if !self.decrypting_failed {
|
||||
if let Some(dn_field) = self.get(HeaderDef::ChatDispositionNotificationTo) {
|
||||
if let Some(dn_field) = self.lookup_field("Chat-Disposition-Notification-To") {
|
||||
if self.get_last_nonmeta().is_some() {
|
||||
let addrs = mailparse::addrparse(&dn_field).unwrap();
|
||||
|
||||
if let Some(dn_to_addr) = addrs.first() {
|
||||
if let Some(from_field) = self.get(HeaderDef::From_) {
|
||||
if let Some(from_field) = self.lookup_field("From") {
|
||||
let from_addrs = mailparse::addrparse(&from_field).unwrap();
|
||||
|
||||
if let Some(from_addr) = from_addrs.first() {
|
||||
@@ -310,7 +316,7 @@ impl<'a> MimeParser<'a> {
|
||||
let mut part = Part::default();
|
||||
part.typ = Viewtype::Text;
|
||||
|
||||
if let Some(ref subject) = self.get_subject() {
|
||||
if let Some(ref subject) = self.subject {
|
||||
if !self.has_chat_version() {
|
||||
part.msg = subject.to_string();
|
||||
}
|
||||
@@ -330,31 +336,12 @@ impl<'a> MimeParser<'a> {
|
||||
self.parts.iter_mut().rev().find(|part| !part.is_meta)
|
||||
}
|
||||
|
||||
pub fn was_encrypted(&self) -> bool {
|
||||
!self.signatures.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn has_chat_version(&self) -> bool {
|
||||
self.header.contains_key("chat-version")
|
||||
}
|
||||
|
||||
pub(crate) fn has_headers(&self) -> bool {
|
||||
!self.header.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn get_subject(&self) -> Option<String> {
|
||||
if let Some(s) = self.get(HeaderDef::Subject) {
|
||||
if s.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(s.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, headerdef: HeaderDef) -> Option<&String> {
|
||||
self.header.get(&headerdef.get_headername())
|
||||
pub fn lookup_field(&self, field_name: &str) -> Option<&String> {
|
||||
self.header.get(&field_name.to_lowercase())
|
||||
}
|
||||
|
||||
fn parse_mime_recursive(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> {
|
||||
@@ -458,9 +445,6 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
}
|
||||
(mime::MULTIPART, "encrypted") => {
|
||||
// we currently do not try to decrypt non-autocrypt messages
|
||||
// at all. If we see an encrypted part, we set
|
||||
// decrypting_failed.
|
||||
let msg_body = self.context.stock_str(StockMessage::CantDecryptMsgBody);
|
||||
let txt = format!("[{}]", msg_body);
|
||||
|
||||
@@ -527,7 +511,7 @@ impl<'a> MimeParser<'a> {
|
||||
let filename = get_attachment_filename(mail);
|
||||
info!(
|
||||
self.context,
|
||||
"add_single_part_if_known {:?} {:?}", mime_type, msg_type
|
||||
"add_single_part_if_known {:?} {:?} {:?}", mime_type, msg_type, filename
|
||||
);
|
||||
|
||||
let old_part_count = self.parts.len();
|
||||
@@ -647,24 +631,50 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
|
||||
fn do_add_single_part(&mut self, mut part: Part) {
|
||||
if self.was_encrypted() {
|
||||
part.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
if self.encrypted {
|
||||
if !self.signatures.is_empty() {
|
||||
part.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
} else {
|
||||
// XXX if the message was encrypted but not signed
|
||||
// it's not neccessarily an error we need to signal.
|
||||
// we could just treat it as if it was not encrypted.
|
||||
part.param.set_int(Param::ErroneousE2ee, 0x2);
|
||||
}
|
||||
}
|
||||
self.parts.push(part);
|
||||
}
|
||||
|
||||
pub fn is_mailinglist_message(&self) -> bool {
|
||||
if self.get(HeaderDef::ListId).is_some() {
|
||||
if self.lookup_field("List-Id").is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(precedence) = self.get(HeaderDef::Precedence) {
|
||||
if let Some(precedence) = self.lookup_field("Precedence") {
|
||||
precedence == "list" || precedence == "bulk"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sender_equals_recipient(&self) -> bool {
|
||||
/* get From: and check there is exactly one sender */
|
||||
if let Some(field) = self.lookup_field("From") {
|
||||
if let Ok(addrs) = mailparse::addrparse(field) {
|
||||
if addrs.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
if let mailparse::MailAddr::Single(ref info) = addrs[0] {
|
||||
let from_addr_norm = addr_normalize(&info.addr);
|
||||
let recipients = get_recipients(self.header.iter());
|
||||
if recipients.len() == 1 && recipients.contains(from_addr_norm) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn repl_msg_by_error(&mut self, error_msg: impl AsRef<str>) {
|
||||
if self.parts.is_empty() {
|
||||
return;
|
||||
@@ -679,11 +689,11 @@ impl<'a> MimeParser<'a> {
|
||||
}
|
||||
|
||||
pub fn get_rfc724_mid(&self) -> Option<String> {
|
||||
if let Some(msgid) = self.get(HeaderDef::MessageId) {
|
||||
parse_message_id(msgid)
|
||||
} else {
|
||||
None
|
||||
// get Message-ID from header
|
||||
if let Some(field) = self.lookup_field("Message-ID") {
|
||||
return parse_message_id(field);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn hash_header(&mut self, fields: &[mailparse::MailHeader<'_>]) {
|
||||
@@ -713,10 +723,9 @@ impl<'a> MimeParser<'a> {
|
||||
let (report_fields, _) = mailparse::parse_headers(&report_body)?;
|
||||
|
||||
// must be present
|
||||
let disp = HeaderDef::Disposition.get_headername();
|
||||
if let Some(_disposition) = report_fields.get_first_value(&disp).ok().flatten() {
|
||||
if let Some(_disposition) = report_fields.get_first_value("Disposition").ok().flatten() {
|
||||
if let Some(original_message_id) = report_fields
|
||||
.get_first_value(&HeaderDef::OriginalMessageId.get_headername())
|
||||
.get_first_value("Original-Message-ID")
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|v| parse_message_id(&v))
|
||||
@@ -1007,7 +1016,7 @@ mod tests {
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(mimeparser.get_subject(), None);
|
||||
assert_eq!(mimeparser.subject, None);
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
|
||||
@@ -1095,14 +1104,14 @@ mod tests {
|
||||
let raw = b"From: hello\n\
|
||||
Content-Type: multipart/mixed; boundary=\"==break==\";\n\
|
||||
Subject: outer-subject\n\
|
||||
Secure-Join-Group: no\n\
|
||||
Test-Header: Bar\nChat-Version: 0.0\n\
|
||||
X-Special-A: special-a\n\
|
||||
Foo: Bar\nChat-Version: 0.0\n\
|
||||
\n\
|
||||
--==break==\n\
|
||||
Content-Type: text/plain; protected-headers=\"v1\";\n\
|
||||
Subject: inner-subject\n\
|
||||
SecureBar-Join-Group: yes\n\
|
||||
Test-Header: Xy\n\
|
||||
X-Special-B: special-b\n\
|
||||
Foo: Xy\n\
|
||||
Chat-Version: 1.0\n\
|
||||
\n\
|
||||
test1\n\
|
||||
@@ -1112,18 +1121,15 @@ mod tests {
|
||||
\x00";
|
||||
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
|
||||
// test that we treat Subject as a protected header that can
|
||||
// bubble upwards
|
||||
assert_eq!(mimeparser.get_subject(), Some("inner-subject".into()));
|
||||
assert_eq!(mimeparser.subject, Some("inner-subject".into()));
|
||||
|
||||
let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap();
|
||||
assert_eq!(of, "no");
|
||||
let of = mimeparser.lookup_field("X-Special-A").unwrap();
|
||||
assert_eq!(of, "special-a");
|
||||
|
||||
// unprotected headers do not bubble upwards
|
||||
let of = mimeparser.get(HeaderDef::_TestHeader).unwrap();
|
||||
let of = mimeparser.lookup_field("Foo").unwrap();
|
||||
assert_eq!(of, "Bar");
|
||||
|
||||
let of = mimeparser.get(HeaderDef::ChatVersion).unwrap();
|
||||
let of = mimeparser.lookup_field("Chat-Version").unwrap();
|
||||
assert_eq!(of, "1.0");
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ impl Params {
|
||||
let file = ParamsFile::from_param(context, val)?;
|
||||
let blob = match file {
|
||||
ParamsFile::FsPath(path) => match create {
|
||||
true => BlobObject::new_from_path(context, path)?,
|
||||
true => BlobObject::create_from_path(context, path)?,
|
||||
false => BlobObject::from_path(context, path)?,
|
||||
},
|
||||
ParamsFile::Blob(blob) => blob,
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::context::Context;
|
||||
use crate::e2ee::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::key::*;
|
||||
use crate::lot::LotState;
|
||||
use crate::message::Message;
|
||||
@@ -351,7 +350,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
"handle_securejoin_handshake(): called with special contact id"
|
||||
);
|
||||
let step = mimeparser
|
||||
.get(HeaderDef::SecureJoin)
|
||||
.lookup_field("Secure-Join")
|
||||
.ok_or_else(|| format_err!("This message is not a Secure-Join message"))?;
|
||||
|
||||
info!(
|
||||
@@ -379,7 +378,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
|
||||
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
|
||||
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
|
||||
let invitenumber = match mimeparser.get(HeaderDef::SecureJoinInvitenumber) {
|
||||
let invitenumber = match mimeparser.lookup_field("Secure-Join-Invitenumber") {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
warn!(context, "Secure-join denied (invitenumber missing).",);
|
||||
@@ -423,7 +422,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
if mimeparser.was_encrypted() {
|
||||
if mimeparser.encrypted {
|
||||
"No valid signature."
|
||||
} else {
|
||||
"Not encrypted."
|
||||
@@ -468,7 +467,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
==== Step 6 in "Out-of-band verified groups" protocol ====
|
||||
============================================================ */
|
||||
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
|
||||
let fingerprint = match mimeparser.get(HeaderDef::SecureJoinFingerprint) {
|
||||
let fingerprint = match mimeparser.lookup_field("Secure-Join-Fingerprint") {
|
||||
Some(fp) => fp,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
@@ -497,7 +496,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
|
||||
let auth_0 = match mimeparser.get(HeaderDef::SecureJoinAuth) {
|
||||
let auth_0 = match mimeparser.lookup_field("Secure-Join-Auth") {
|
||||
Some(auth) => auth,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
@@ -527,7 +526,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
inviter_progress!(context, contact_id, 600);
|
||||
if join_vg {
|
||||
let field_grpid = mimeparser
|
||||
.get(HeaderDef::SecureJoinGroup)
|
||||
.lookup_field("Secure-Join-Group")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or_else(|| "");
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, field_grpid);
|
||||
@@ -601,10 +600,10 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
|
||||
emit_event!(context, Event::ContactsChanged(None));
|
||||
let cg_member_added = mimeparser
|
||||
.get(HeaderDef::ChatGroupMemberAdded)
|
||||
.lookup_field("Chat-Group-Member-Added")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or_else(|| "");
|
||||
if join_vg && !context.is_self_addr(cg_member_added)? {
|
||||
if join_vg && !addr_equals_self(context, cg_member_added) {
|
||||
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
|
||||
return Ok(ret);
|
||||
}
|
||||
@@ -636,7 +635,7 @@ pub(crate) fn handle_securejoin_handshake(
|
||||
inviter_progress!(context, contact_id, 800);
|
||||
inviter_progress!(context, contact_id, 1000);
|
||||
let field_grpid = mimeparser
|
||||
.get(HeaderDef::SecureJoinGroup)
|
||||
.lookup_field("Secure-Join-Group")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or_else(|| "");
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
|
||||
@@ -718,7 +717,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
******************************************************************************/
|
||||
|
||||
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
|
||||
if !mimeparser.was_encrypted() {
|
||||
if !mimeparser.encrypted {
|
||||
warn!(mimeparser.context, "Message not encrypted.",);
|
||||
false
|
||||
} else if mimeparser.signatures.is_empty() {
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
pub mod send;
|
||||
|
||||
use async_smtp::smtp::client::net::*;
|
||||
use async_smtp::*;
|
||||
|
||||
use async_std::task;
|
||||
use lettre::smtp::client::net::*;
|
||||
use lettre::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
@@ -21,12 +19,12 @@ pub enum Error {
|
||||
InvalidLoginAddress {
|
||||
address: String,
|
||||
#[cause]
|
||||
error: async_smtp::error::Error,
|
||||
error: lettre::error::Error,
|
||||
},
|
||||
#[fail(display = "SMTP failed to connect: {:?}", _0)]
|
||||
ConnectionFailure(#[cause] async_smtp::smtp::error::Error),
|
||||
ConnectionFailure(#[cause] lettre::smtp::error::Error),
|
||||
#[fail(display = "SMTP: failed to setup connection {:?}", _0)]
|
||||
ConnectionSetupFailure(#[cause] async_smtp::smtp::error::Error),
|
||||
ConnectionSetupFailure(#[cause] lettre::smtp::error::Error),
|
||||
#[fail(display = "SMTP: oauth2 error {:?}", _0)]
|
||||
Oauth2Error { address: String },
|
||||
}
|
||||
@@ -36,7 +34,8 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(Default, DebugStub)]
|
||||
pub struct Smtp {
|
||||
#[debug_stub(some = "SmtpTransport")]
|
||||
transport: Option<async_smtp::smtp::SmtpTransport>,
|
||||
transport: Option<lettre::smtp::SmtpTransport>,
|
||||
transport_connected: bool,
|
||||
/// Email address we are sending from.
|
||||
from: Option<EmailAddress>,
|
||||
}
|
||||
@@ -49,12 +48,16 @@ impl Smtp {
|
||||
|
||||
/// Disconnect the SMTP transport and drop it entirely.
|
||||
pub fn disconnect(&mut self) {
|
||||
if let Some(ref mut transport) = self.transport.take() {
|
||||
transport.close();
|
||||
if self.transport.is_none() || !self.transport_connected {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut transport = self.transport.take().unwrap();
|
||||
transport.close();
|
||||
self.transport_connected = false;
|
||||
}
|
||||
|
||||
/// check whether we are connected
|
||||
/// Check if a connection already exists.
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.transport.is_some()
|
||||
}
|
||||
@@ -96,21 +99,21 @@ impl Smtp {
|
||||
}
|
||||
let user = &lp.send_user;
|
||||
(
|
||||
async_smtp::smtp::authentication::Credentials::new(
|
||||
lettre::smtp::authentication::Credentials::new(
|
||||
user.to_string(),
|
||||
access_token.unwrap_or_default(),
|
||||
),
|
||||
vec![async_smtp::smtp::authentication::Mechanism::Xoauth2],
|
||||
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
|
||||
)
|
||||
} else {
|
||||
// plain
|
||||
let user = lp.send_user.clone();
|
||||
let pw = lp.send_pw.clone();
|
||||
(
|
||||
async_smtp::smtp::authentication::Credentials::new(user, pw),
|
||||
lettre::smtp::authentication::Credentials::new(user, pw),
|
||||
vec![
|
||||
async_smtp::smtp::authentication::Mechanism::Plain,
|
||||
async_smtp::smtp::authentication::Mechanism::Login,
|
||||
lettre::smtp::authentication::Mechanism::Plain,
|
||||
lettre::smtp::authentication::Mechanism::Login,
|
||||
],
|
||||
)
|
||||
};
|
||||
@@ -118,26 +121,24 @@ impl Smtp {
|
||||
let security = if 0
|
||||
!= lp.server_flags & (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_PLAIN) as i32
|
||||
{
|
||||
async_smtp::smtp::ClientSecurity::Opportunistic(tls_parameters)
|
||||
lettre::smtp::ClientSecurity::Opportunistic(tls_parameters)
|
||||
} else {
|
||||
async_smtp::smtp::ClientSecurity::Wrapper(tls_parameters)
|
||||
lettre::smtp::ClientSecurity::Wrapper(tls_parameters)
|
||||
};
|
||||
|
||||
let client = task::block_on(async_smtp::smtp::SmtpClient::with_security(
|
||||
(domain.as_str(), port),
|
||||
security,
|
||||
))
|
||||
.map_err(Error::ConnectionSetupFailure)?;
|
||||
let client = lettre::smtp::SmtpClient::new((domain.as_str(), port), security)
|
||||
.map_err(Error::ConnectionSetupFailure)?;
|
||||
|
||||
let client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(async_smtp::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
let mut trans = client.into_transport();
|
||||
task::block_on(trans.connect()).map_err(Error::ConnectionFailure)?;
|
||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
let mut trans = client.transport();
|
||||
trans.connect().map_err(Error::ConnectionFailure)?;
|
||||
|
||||
self.transport = Some(trans);
|
||||
self.transport_connected = true;
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! # SMTP message sending
|
||||
|
||||
use super::Smtp;
|
||||
use async_smtp::*;
|
||||
use lettre::*;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::events::Event;
|
||||
@@ -11,9 +11,9 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "Envelope error: {}", _0)]
|
||||
EnvelopeError(#[cause] async_smtp::error::Error),
|
||||
EnvelopeError(#[cause] lettre::error::Error),
|
||||
#[fail(display = "Send error: {}", _0)]
|
||||
SendError(#[cause] async_smtp::smtp::error::Error),
|
||||
SendError(#[cause] lettre::smtp::error::Error),
|
||||
#[fail(display = "SMTP has no transport")]
|
||||
NoTransport,
|
||||
}
|
||||
@@ -21,7 +21,7 @@ pub enum Error {
|
||||
impl Smtp {
|
||||
/// Send a prepared mail to recipients.
|
||||
/// On successful send out Ok() is returned.
|
||||
pub async fn send(
|
||||
pub fn send(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
recipients: Vec<EmailAddress>,
|
||||
@@ -36,21 +36,22 @@ impl Smtp {
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let envelope =
|
||||
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
|
||||
let mail = SendableEmail::new(
|
||||
envelope,
|
||||
format!("{}", job_id), // only used for internal logging
|
||||
message,
|
||||
);
|
||||
if let Some(ref mut transport) = self.transport {
|
||||
transport.send(mail).await.map_err(Error::SendError)?;
|
||||
let envelope =
|
||||
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
|
||||
let mail = SendableEmail::new(
|
||||
envelope,
|
||||
format!("{}", job_id), // only used for internal logging
|
||||
message,
|
||||
);
|
||||
|
||||
transport.send(mail).map_err(Error::SendError)?;
|
||||
|
||||
context.call_cb(Event::SmtpMessageSent(format!(
|
||||
"Message len={} was smtp-sent to {}",
|
||||
message_len, recipients_display
|
||||
)));
|
||||
|
||||
self.transport_connected = true;
|
||||
Ok(())
|
||||
} else {
|
||||
warn!(
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
use deltachat::config;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::keyring::*;
|
||||
use deltachat::pgp;
|
||||
@@ -225,3 +227,20 @@ fn test_stress_tests() {
|
||||
let context = create_test_context();
|
||||
stress_functions(&context.ctx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat() {
|
||||
let context = create_test_context();
|
||||
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(contact1, 0);
|
||||
|
||||
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert!(chat_id > 9, "chat_id too small {}", chat_id);
|
||||
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
|
||||
|
||||
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert_eq!(chat2_id, chat_id);
|
||||
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
|
||||
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user