mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 13:36:30 +03:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c5eb0ae37 | ||
|
|
36bce6c468 | ||
|
|
19a32cdfd3 | ||
|
|
d708f386a1 | ||
|
|
0f837f4bed | ||
|
|
242e8e2bb3 | ||
|
|
1d56b24b67 | ||
|
|
bb9138708a | ||
|
|
34f5510f1f | ||
|
|
6c6d47c89c | ||
|
|
196075c031 | ||
|
|
2e5e8f73c6 | ||
|
|
ada5d38272 | ||
|
|
c4b0f773db | ||
|
|
276daf631e | ||
|
|
fb19b58147 | ||
|
|
13a5e3cf6f | ||
|
|
1caf3caf1b | ||
|
|
564370f79a |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,5 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## 1.76.0
|
||||
|
||||
### Changes
|
||||
- move messages in batches #3058
|
||||
- delete messages in batches #3060
|
||||
- python: remove arbitrary timeouts from tests #3059
|
||||
- refactorings #3026
|
||||
|
||||
### Fixes
|
||||
- avoid archived, fresh chats #3053
|
||||
- treat "NO" IMAP response to MOVE and COPY commands as an error #3058
|
||||
- Fix a bug where messages in the Spam folder created contact requests #3015
|
||||
- Fix a bug where drafts disappeared after some days #3067
|
||||
- Parse MS Exchange read receipts and mark the original message as read #3075
|
||||
- do not retry message sending infinitely in case of permanent SMTP failure #3070
|
||||
- set message state to failed when retry limit is exceeded #3072
|
||||
|
||||
|
||||
## 1.75.0
|
||||
|
||||
### Changes
|
||||
|
||||
183
Cargo.lock
generated
183
Cargo.lock
generated
@@ -242,7 +242,7 @@ dependencies = [
|
||||
"parking",
|
||||
"polling",
|
||||
"slab",
|
||||
"socket2 0.4.3",
|
||||
"socket2 0.4.4",
|
||||
"waker-fn",
|
||||
"winapi",
|
||||
]
|
||||
@@ -347,9 +347,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-std-resolver"
|
||||
version = "0.20.3"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed4e2c3da14d8ad45acb1e3191db7a918e9505b6f155b218e70a7c9a1a48c638"
|
||||
checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -374,9 +374,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.0.3"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
|
||||
checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
@@ -408,15 +408,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
@@ -493,9 +496,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.0"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
@@ -719,9 +722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "4.4.0"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4d3d118de1bf9678546f65e12f749b46abb5a56129d435af21fc7e42768f974"
|
||||
checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
"str-buf",
|
||||
@@ -779,9 +782,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -816,9 +819,9 @@ checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
@@ -882,9 +885,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.6"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762"
|
||||
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
@@ -895,9 +898,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110"
|
||||
checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
@@ -905,9 +908,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
|
||||
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
@@ -915,9 +918,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
|
||||
checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
@@ -1063,7 +1066,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.75.0"
|
||||
version = "1.76.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
@@ -1143,7 +1146,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.75.0"
|
||||
version = "1.76.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -1203,13 +1206,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
|
||||
checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.0",
|
||||
"block-buffer 0.10.2",
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1590,9 +1592,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1605,9 +1607,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
|
||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1615,15 +1617,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -1632,9 +1634,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@@ -1653,9 +1655,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1664,21 +1666,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508"
|
||||
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
|
||||
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.19"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1752,15 +1754,14 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f16c88aa13d2656ef20d1c042086b8767bbe2bdb62526894275a1b062161b2e"
|
||||
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1894,9 +1895,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.5.1"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
|
||||
checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
|
||||
|
||||
[[package]]
|
||||
name = "human-panic"
|
||||
@@ -2121,15 +2122,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.114"
|
||||
version = "0.2.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50"
|
||||
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
@@ -2156,9 +2157,9 @@ checksum = "95f5690fef754d905294c56f7ac815836f2513af966aa47f2e07ac79be07827f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
@@ -2228,7 +2229,7 @@ version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2263,7 +2264,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2352,7 +2353,7 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
@@ -2363,7 +2364,7 @@ version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7",
|
||||
"autocfg 0.1.8",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"libm",
|
||||
@@ -2393,7 +2394,7 @@ version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
@@ -2403,7 +2404,7 @@ version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
@@ -2414,7 +2415,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
@@ -2425,7 +2426,7 @@ version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2500,7 +2501,7 @@ version = "0.9.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
@@ -2997,7 +2998,7 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"autocfg 1.1.0",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -3151,7 +3152,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver 1.0.4",
|
||||
"semver 1.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3256,9 +3257,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.5.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d09d3c15d814eda1d6a836f2f2b56a6abc1446c8a34351cb3180d3db92ffe4ce"
|
||||
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
@@ -3269,9 +3270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.5.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e90dd10c41c6bfc633da6e0c659bd25d31e0791e5974ac42970267d59eba87f7"
|
||||
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -3288,9 +3289,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
|
||||
checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7"
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
@@ -3330,9 +3331,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.78"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
@@ -3383,7 +3384,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.10.1",
|
||||
"digest 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3422,7 +3423,7 @@ checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.10.1",
|
||||
"digest 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3513,9 +3514,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f82496b90c36d70af5fcd482edaa2e0bd16fade569de1330405fecbbdac736b"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
@@ -3852,9 +3853,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.15.0"
|
||||
version = "1.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
|
||||
checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
@@ -3870,9 +3871,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-proto"
|
||||
version = "0.20.3"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4"
|
||||
checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -3894,9 +3895,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-resolver"
|
||||
version = "0.20.3"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770"
|
||||
checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"futures-util",
|
||||
@@ -3972,9 +3973,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.75.0"
|
||||
version = "1.76.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
@@ -24,7 +24,7 @@ async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = { version = "0.3" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", features = ["socks5"] }
|
||||
async-std-resolver = "0.20"
|
||||
async-std = { version = "1", features = ["unstable"] }
|
||||
async-std = { version = "1" }
|
||||
async-tar = { version = "0.4", default-features=false }
|
||||
async-trait = "0.1"
|
||||
backtrace = "0.3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.75.0"
|
||||
version = "1.76.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -224,9 +224,7 @@ class DirectImap:
|
||||
""" (blocking) wait for next idle message from server. """
|
||||
assert self._idling
|
||||
self.account.log("imap-direct: calling idle_check")
|
||||
res = self.conn.idle_check(timeout=30)
|
||||
if len(res) == 0:
|
||||
raise TimeoutError
|
||||
res = self.conn.idle_check()
|
||||
if terminate:
|
||||
self.idle_done()
|
||||
self.account.log("imap-direct: idle_check returned {!r}".format(res))
|
||||
|
||||
@@ -241,7 +241,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
|
||||
def make_account(self, path, logid, quiet=False):
|
||||
ac = Account(path, logging=self._logging)
|
||||
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
|
||||
ac._evtracker.set_timeout(30)
|
||||
ac.addr = ac.get_self_contact().addr
|
||||
ac.set_config("displayname", logid)
|
||||
if not quiet:
|
||||
@@ -483,7 +482,7 @@ class BotProcess:
|
||||
def kill(self) -> None:
|
||||
self.popen.kill()
|
||||
|
||||
def wait(self, timeout=30) -> None:
|
||||
def wait(self, timeout=None) -> None:
|
||||
self.popen.wait(timeout=timeout)
|
||||
|
||||
def fnmatch_lines(self, pattern_lines):
|
||||
@@ -492,7 +491,7 @@ class BotProcess:
|
||||
print("+++FNMATCH:", next_pattern)
|
||||
ignored = []
|
||||
while 1:
|
||||
line = self.stdout_queue.get(timeout=15)
|
||||
line = self.stdout_queue.get()
|
||||
if line is None:
|
||||
if ignored:
|
||||
print("BOT stdout terminated after these lines")
|
||||
|
||||
@@ -652,8 +652,6 @@ class TestOnlineAccount:
|
||||
pre_generated_key=False,
|
||||
config={"key_gen_type": str(const.DC_KEY_GEN_ED25519)}
|
||||
)
|
||||
# rsa key gen can be slow especially on CI, adjust timeout
|
||||
ac1._evtracker.set_timeout(240)
|
||||
acfactory.wait_configure_and_start_io()
|
||||
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||
|
||||
@@ -892,11 +890,11 @@ class TestOnlineAccount:
|
||||
|
||||
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||
chat.send_text("message1")
|
||||
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
chat.send_text("message2")
|
||||
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
chat.send_text("message3")
|
||||
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
def test_forward_messages(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
@@ -1400,11 +1398,13 @@ class TestOnlineAccount:
|
||||
assert not device_chat.can_send()
|
||||
assert device_chat.get_draft() is None
|
||||
|
||||
def test_dont_show_emails_in_draft_folder(self, acfactory, lp):
|
||||
def test_dont_show_emails(self, acfactory, lp):
|
||||
"""Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them.
|
||||
So: If it's outgoing AND there is no Received header AND it's not in the sentbox, then ignore the email.
|
||||
|
||||
If the draft email is sent out later (i.e. moved to "Sent"), it must be shown."""
|
||||
If the draft email is sent out later (i.e. moved to "Sent"), it must be shown.
|
||||
|
||||
Also, test that unknown emails in the Spam folder are not shown."""
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac1.set_config("show_emails", "2")
|
||||
ac1.create_contact("alice@example.org").create_chat()
|
||||
@@ -1412,6 +1412,7 @@ class TestOnlineAccount:
|
||||
acfactory.wait_configure(ac1)
|
||||
ac1.direct_imap.create_folder("Drafts")
|
||||
ac1.direct_imap.create_folder("Sent")
|
||||
ac1.direct_imap.create_folder("Spam")
|
||||
|
||||
acfactory.wait_configure_and_start_io()
|
||||
# Wait until each folder was selected once and we are IDLEing again:
|
||||
@@ -1436,6 +1437,15 @@ class TestOnlineAccount:
|
||||
|
||||
message in Sent
|
||||
""".format(ac1.get_config("configured_addr")))
|
||||
ac1.direct_imap.append("Spam", """
|
||||
From: unknown.address@junk.org
|
||||
Subject: subj
|
||||
To: {}
|
||||
Message-ID: <spam.message@junk.org>
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Unknown message in Spam
|
||||
""".format(ac1.get_config("configured_addr")))
|
||||
|
||||
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
||||
lp.sec("All prepared, now let DC find the message")
|
||||
@@ -1449,6 +1459,10 @@ class TestOnlineAccount:
|
||||
assert msg.text == "subj – message in Sent"
|
||||
assert len(msg.chat.get_messages()) == 1
|
||||
|
||||
assert not any("unknown.address" in c.get_name() for c in ac1.get_chats())
|
||||
ac1.direct_imap.select_folder("Spam")
|
||||
assert ac1.direct_imap.get_uid_by_message_id("spam.message@junk.org")
|
||||
|
||||
ac1.stop_io()
|
||||
lp.sec("'Send out' the draft, i.e. move it to the Sent folder, and wait for DC to display it this time")
|
||||
ac1.direct_imap.select_folder("Drafts")
|
||||
@@ -1834,7 +1848,6 @@ class TestOnlineAccount:
|
||||
lp.sec("trigger ac setup message and return setupcode")
|
||||
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
||||
setup_code = ac1.initiate_key_transfer()
|
||||
ac2._evtracker.set_timeout(30)
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev.data2)
|
||||
assert msg.is_setup_message()
|
||||
@@ -1851,7 +1864,6 @@ class TestOnlineAccount:
|
||||
def test_ac_setup_message_twice(self, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
ac2 = acfactory.clone_online_account(ac1)
|
||||
ac2._evtracker.set_timeout(30)
|
||||
acfactory.wait_configure_and_start_io()
|
||||
|
||||
lp.sec("trigger ac setup message but ignore")
|
||||
@@ -2025,7 +2037,7 @@ class TestOnlineAccount:
|
||||
|
||||
lp.sec("ac1: send a message to group chat to promote the group")
|
||||
chat.send_text("afterwards promoted")
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "chat-modified"
|
||||
assert chat.is_promoted()
|
||||
assert sorted(x.addr for x in chat.get_contacts()) == \
|
||||
@@ -2035,29 +2047,29 @@ class TestOnlineAccount:
|
||||
# note that if the above create_chat() would not
|
||||
# happen we would not receive a proper member_added event
|
||||
contact2 = chat.add_contact("devnull@testrun.org")
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "chat-modified"
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "chat-modified"
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "added"
|
||||
assert ev.message.get_sender_contact().addr == ac1_addr
|
||||
assert ev.contact.addr == "devnull@testrun.org"
|
||||
|
||||
lp.sec("ac1: remove address2")
|
||||
chat.remove_contact(contact2)
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "chat-modified"
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "removed"
|
||||
assert ev.contact.addr == contact2.addr
|
||||
assert ev.message.get_sender_contact().addr == ac1_addr
|
||||
|
||||
lp.sec("ac1: remove ac2 contact from chat")
|
||||
chat.remove_contact(ac2)
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "chat-modified"
|
||||
ev = in_list.get(timeout=10)
|
||||
ev = in_list.get()
|
||||
assert ev.action == "removed"
|
||||
assert ev.message.get_sender_contact().addr == ac1_addr
|
||||
|
||||
@@ -2504,8 +2516,7 @@ class TestOnlineAccount:
|
||||
lp.sec("ac2: deleting all messages except third")
|
||||
assert len(to_delete) == len(texts) - 1
|
||||
ac2.delete_messages(to_delete)
|
||||
for msg in to_delete:
|
||||
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
|
||||
|
||||
ac2._evtracker.get_info_contains("close/expunge succeeded")
|
||||
|
||||
@@ -2682,7 +2693,13 @@ class TestOnlineAccount:
|
||||
ac1.direct_imap.select_config_folder("inbox")
|
||||
ac1.direct_imap.idle_start()
|
||||
acfactory.get_accepted_chat(ac2, ac1).send_text("hello")
|
||||
ac1.direct_imap.idle_check(terminate=True)
|
||||
while True:
|
||||
if len(ac1.direct_imap.idle_check(terminate=True)) > 1:
|
||||
# If length is 1, it's [(b'OK', b'Still here')]
|
||||
# Could happen on very slow network.
|
||||
#
|
||||
# More is usually [(1, b'EXISTS'), (1, b'RECENT')]
|
||||
break
|
||||
ac1.direct_imap.conn.move(["*"], folder) # "*" means "biggest UID in mailbox"
|
||||
|
||||
lp.sec("Everything prepared, now see if DeltaChat finds the message (" + variant + ")")
|
||||
|
||||
@@ -79,7 +79,7 @@ addopts = -v -ra --strict-markers
|
||||
norecursedirs = .tox
|
||||
xfail_strict=true
|
||||
timeout = 90
|
||||
timeout_method = thread
|
||||
timeout_func_only = True
|
||||
markers =
|
||||
ignored: ignore this test in default test runs, use --ignored to run.
|
||||
|
||||
|
||||
31
src/chat.rs
31
src/chat.rs
@@ -474,22 +474,21 @@ impl ChatId {
|
||||
self
|
||||
);
|
||||
|
||||
if visibility == ChatVisibility::Archived {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
|
||||
paramsv![MessageState::InNoticed, self, MessageState::InFresh],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats SET archived=? WHERE id=?;",
|
||||
paramsv![visibility, self],
|
||||
)
|
||||
.transaction(move |transaction| {
|
||||
if visibility == ChatVisibility::Archived {
|
||||
transaction.execute(
|
||||
"UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
|
||||
paramsv![MessageState::InNoticed, self, MessageState::InFresh],
|
||||
)?;
|
||||
}
|
||||
transaction.execute(
|
||||
"UPDATE chats SET archived=? WHERE id=?;",
|
||||
paramsv![visibility, self],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
@@ -961,7 +960,7 @@ impl std::fmt::Display for ChatId {
|
||||
/// well as query for a [ChatId].
|
||||
impl rusqlite::types::ToSql for ChatId {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let val = rusqlite::types::Value::Integer(self.0 as i64);
|
||||
let val = rusqlite::types::Value::Integer(i64::from(self.0));
|
||||
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||
Ok(out)
|
||||
}
|
||||
@@ -971,7 +970,7 @@ impl rusqlite::types::ToSql for ChatId {
|
||||
impl rusqlite::types::FromSql for ChatId {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
i64::column_result(value).and_then(|val| {
|
||||
if 0 <= val && val <= std::u32::MAX as i64 {
|
||||
if 0 <= val && val <= i64::from(std::u32::MAX) {
|
||||
Ok(ChatId::new(val as u32))
|
||||
} else {
|
||||
Err(rusqlite::types::FromSqlError::OutOfRange(val))
|
||||
|
||||
@@ -245,7 +245,7 @@ impl Context {
|
||||
match self.get_config_int(Config::DeleteServerAfter).await? {
|
||||
0 => Ok(None),
|
||||
1 => Ok(Some(0)),
|
||||
x => Ok(Some(x as i64)),
|
||||
x => Ok(Some(i64::from(x))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ impl Context {
|
||||
pub async fn get_config_delete_device_after(&self) -> Result<Option<i64>> {
|
||||
match self.get_config_int(Config::DeleteDeviceAfter).await? {
|
||||
0 => Ok(None),
|
||||
x => Ok(Some(x as i64)),
|
||||
x => Ok(Some(i64::from(x))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,6 @@ pub(crate) async fn dc_receive_imf_inner(
|
||||
&mut mime_parser,
|
||||
imf_raw,
|
||||
incoming,
|
||||
incoming_origin,
|
||||
server_folder,
|
||||
&to_ids,
|
||||
&rfc724_mid,
|
||||
@@ -415,7 +414,6 @@ async fn add_parts(
|
||||
mime_parser: &mut MimeMessage,
|
||||
imf_raw: &[u8],
|
||||
incoming: bool,
|
||||
incoming_origin: Origin,
|
||||
server_folder: &str,
|
||||
to_ids: &[u32],
|
||||
rfc724_mid: &str,
|
||||
@@ -432,7 +430,6 @@ async fn add_parts(
|
||||
) -> Result<ReceivedMsg> {
|
||||
let mut chat_id = None;
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut incoming_origin = incoming_origin;
|
||||
|
||||
let parent = get_parent_message(context, mime_parser).await?;
|
||||
|
||||
@@ -676,7 +673,6 @@ async fn add_parts(
|
||||
if chat_id_blocked != Blocked::Not {
|
||||
if chat_id_blocked != create_blocked {
|
||||
chat_id.set_blocked(context, create_blocked).await?;
|
||||
chat_id_blocked = create_blocked;
|
||||
}
|
||||
if create_blocked == Blocked::Request && parent.is_some() {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
@@ -687,9 +683,6 @@ async fn add_parts(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
);
|
||||
if !incoming_origin.is_known() {
|
||||
incoming_origin = Origin::IncomingReplyTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -701,15 +694,6 @@ async fn add_parts(
|
||||
} else {
|
||||
MessageState::InFresh
|
||||
};
|
||||
|
||||
let is_spam = (chat_id_blocked == Blocked::Request)
|
||||
&& !incoming_origin.is_known()
|
||||
&& (is_dc_message == MessengerMessage::No)
|
||||
&& context.is_spam_folder(server_folder).await?;
|
||||
if is_spam {
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message is probably spam (TRASH)");
|
||||
}
|
||||
} else {
|
||||
// Outgoing
|
||||
|
||||
@@ -1156,8 +1140,8 @@ INSERT INTO msgs
|
||||
stmt.execute(paramsv![
|
||||
rfc724_mid,
|
||||
chat_id,
|
||||
if trash { 0 } else { from_id as i32 },
|
||||
if trash { 0 } else { to_id as i32 },
|
||||
if trash { 0 } else { i64::from(from_id) },
|
||||
if trash { 0 } else { i64::from(to_id) },
|
||||
sort_timestamp,
|
||||
sent_timestamp,
|
||||
rcvd_timestamp,
|
||||
@@ -2363,12 +2347,10 @@ fn dc_create_incoming_rfc724_mid(mime: &MimeMessage) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chat::get_chat_contacts;
|
||||
|
||||
use mailparse::MailHeaderMap;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::chat::get_chat_contacts;
|
||||
use crate::chat::{get_chat_msgs, ChatItem, ChatVisibility};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::constants::{DC_CONTACT_ID_INFO, DC_GCL_NO_SPECIALS};
|
||||
@@ -2977,7 +2959,7 @@ mod tests {
|
||||
&headers,
|
||||
"some-other-message-id",
|
||||
std::iter::empty(),
|
||||
ShowEmails::Off
|
||||
ShowEmails::Off,
|
||||
)
|
||||
.await
|
||||
.unwrap());
|
||||
@@ -4298,76 +4280,6 @@ YEAAAAAA!.
|
||||
assert_eq!(msg.text.unwrap(), "Reply");
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_dont_show_spam() {
|
||||
async fn is_shown(t: &TestContext, raw: &[u8], server_folder: &str) -> bool {
|
||||
let mail = mailparse::parse_mail(raw).unwrap();
|
||||
dc_receive_imf(t, raw, server_folder, false).await.unwrap();
|
||||
t.get_last_msg().await.rfc724_mid
|
||||
== mail.get_headers().get_first_value("Message-Id").unwrap()
|
||||
}
|
||||
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::ConfiguredSpamFolder, Some("Spam"))
|
||||
.await
|
||||
.unwrap();
|
||||
t.set_config(Config::ShowEmails, Some("2")).await.unwrap();
|
||||
|
||||
assert!(
|
||||
is_shown(
|
||||
&t,
|
||||
b"Message-Id: abcd1@exmaple.com\n\
|
||||
From: bob@example.org\n\
|
||||
Chat-Version: 1.0\n",
|
||||
"Inbox",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
assert!(
|
||||
is_shown(
|
||||
&t,
|
||||
b"Message-Id: abcd2@exmaple.com\n\
|
||||
From: bob@example.org\n",
|
||||
"Inbox",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
assert!(
|
||||
is_shown(
|
||||
&t,
|
||||
b"Message-Id: abcd3@exmaple.com\n\
|
||||
From: bob@example.org\n\
|
||||
Chat-Version: 1.0\n",
|
||||
"Spam",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
assert!(
|
||||
// Note the `!`:
|
||||
!is_shown(
|
||||
&t,
|
||||
b"Message-Id: abcd4@exmaple.com\n\
|
||||
From: bob@example.org\n",
|
||||
"Spam",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
Contact::create(&t, "", "bob@example.org").await.unwrap();
|
||||
assert!(
|
||||
is_shown(
|
||||
&t,
|
||||
b"Message-Id: abcd5@exmaple.com\n\
|
||||
From: bob@example.org\n",
|
||||
"Spam",
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_dont_show_all_outgoing_msgs_in_self_chat() {
|
||||
// Regression test for <https://github.com/deltachat/deltachat-android/issues/1940>:
|
||||
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn dc_gm2local_offset() -> i64 {
|
||||
/* returns the offset that must be _added_ to an UTC/GMT-time to create the localtime.
|
||||
the function may return negative values. */
|
||||
let lt = Local::now();
|
||||
lt.offset().local_minus_utc() as i64
|
||||
i64::from(lt.offset().local_minus_utc())
|
||||
}
|
||||
|
||||
// timesmearing
|
||||
|
||||
422
src/imap.rs
422
src/imap.rs
@@ -9,7 +9,7 @@ use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, format_err, Context as _, Result};
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use async_imap::types::{
|
||||
Fetch, Flag, Mailbox, Name, NameAttribute, Quota, QuotaRoot, UnsolicitedResponse,
|
||||
};
|
||||
@@ -458,12 +458,9 @@ impl Imap {
|
||||
.await
|
||||
.context("fetch_new_messages")?;
|
||||
|
||||
self.move_messages(context, watch_folder)
|
||||
self.move_delete_messages(context, watch_folder)
|
||||
.await
|
||||
.context("move_messages")?;
|
||||
self.delete_messages(context, watch_folder)
|
||||
.await
|
||||
.context("delete_messages")?;
|
||||
.context("move_delete_messages")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -751,6 +748,11 @@ impl Imap {
|
||||
// message, move it to the movebox and then download the second message before
|
||||
// downloading the first one, if downloading from inbox before moving is allowed.
|
||||
if folder == target
|
||||
// Never download messages directly from the spam folder.
|
||||
// If the sender is known, the message will be moved to the Inbox or Mvbox
|
||||
// and then we download the message from there.
|
||||
// Also see `spam_target_folder()`.
|
||||
&& !context.is_spam_folder(folder).await?
|
||||
&& prefetch_should_download(
|
||||
context,
|
||||
&headers,
|
||||
@@ -827,18 +829,128 @@ impl Imap {
|
||||
Ok(read_cnt > 0)
|
||||
}
|
||||
|
||||
/// Moves messages.
|
||||
/// Deletes batch of messages identified by their UID from the currently
|
||||
/// selected folder.
|
||||
async fn delete_message_batch(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
uid_set: &str,
|
||||
row_ids: Vec<i64>,
|
||||
) -> Result<()> {
|
||||
// mark the message for deletion
|
||||
self.add_flag_finalized_with_set(uid_set, "\\Deleted")
|
||||
.await?;
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
format!(
|
||||
"DELETE FROM imap WHERE id IN ({})",
|
||||
row_ids.iter().map(|_| "?").collect::<Vec<&str>>().join(",")
|
||||
),
|
||||
rusqlite::params_from_iter(row_ids),
|
||||
)
|
||||
.await
|
||||
.context("cannot remove deleted messages from imap table")?;
|
||||
|
||||
context.emit_event(EventType::ImapMessageDeleted(format!(
|
||||
"IMAP messages {} marked as deleted",
|
||||
uid_set
|
||||
)));
|
||||
self.config.selected_folder_needs_expunge = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Moves batch of messages identified by their UID from the currently
|
||||
/// selected folder to the target folder.
|
||||
async fn move_message_batch(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
set: &str,
|
||||
row_ids: Vec<i64>,
|
||||
target: &str,
|
||||
) -> Result<()> {
|
||||
if self.config.can_move {
|
||||
let session = self
|
||||
.session
|
||||
.as_mut()
|
||||
.context("no session while attempting to MOVE messages")?;
|
||||
match session.uid_mv(set, &target).await {
|
||||
Ok(()) => {
|
||||
// Messages are moved or don't exist, IMAP returns OK response in both cases.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
format!(
|
||||
"DELETE FROM imap WHERE id IN ({})",
|
||||
row_ids.iter().map(|_| "?").collect::<Vec<&str>>().join(",")
|
||||
),
|
||||
rusqlite::params_from_iter(row_ids),
|
||||
)
|
||||
.await
|
||||
.context("cannot delete moved messages from imap table")?;
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP messages {} moved to {}",
|
||||
set, target
|
||||
)));
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot move message, fallback to COPY/DELETE {} to {}: {}",
|
||||
set,
|
||||
target,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Server does not support MOVE, fallback to COPY/DELETE {} to {}", set, target
|
||||
);
|
||||
}
|
||||
|
||||
// Server does not support MOVE or MOVE failed.
|
||||
// Copy the message to the destination folder and mark the record for deletion.
|
||||
let session = self
|
||||
.session
|
||||
.as_mut()
|
||||
.context("no session while attempting to COPY messages")?;
|
||||
match session.uid_copy(&set, &target).await {
|
||||
Ok(()) => {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
format!(
|
||||
"UPDATE imap SET target='' WHERE id IN ({})",
|
||||
row_ids.iter().map(|_| "?").collect::<Vec<&str>>().join(",")
|
||||
),
|
||||
rusqlite::params_from_iter(row_ids),
|
||||
)
|
||||
.await
|
||||
.context("cannot plan deletion of copied messages")?;
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP messages {} copied to {}",
|
||||
set, target
|
||||
)));
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves and deletes messages as planned in the `imap` table.
|
||||
///
|
||||
/// This is the only place where messages are moved on the IMAP server.
|
||||
async fn move_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
||||
let rows = context
|
||||
/// This is the only place where messages are moved or deleted on the IMAP server.
|
||||
async fn move_delete_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
||||
let mut rows = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, uid, target FROM imap
|
||||
WHERE folder = ?
|
||||
AND target != folder
|
||||
AND target != '' -- Not planned for deletion.
|
||||
ORDER BY id",
|
||||
ORDER BY target, uid",
|
||||
paramsv![folder],
|
||||
|row| {
|
||||
let rowid: i64 = row.get(0)?;
|
||||
@@ -848,147 +960,62 @@ impl Imap {
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.into_iter()
|
||||
.peekable();
|
||||
|
||||
self.prepare(context).await?;
|
||||
self.select_folder(context, Some(folder)).await?;
|
||||
|
||||
for (rowid, uid, target) in rows {
|
||||
// TODO: batch moves of messages with the same destination.
|
||||
let set = uid.to_string();
|
||||
while let Some((_, _, target)) = rows.peek().cloned() {
|
||||
// Construct next request for the target folder.
|
||||
let mut uid_set = String::new();
|
||||
let mut rowid_set = Vec::new();
|
||||
|
||||
if self.config.can_move {
|
||||
if let Some(session) = &mut self.session {
|
||||
match session.uid_mv(&set, &target).await {
|
||||
Ok(_) => {
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP message {}/{} moved to {}",
|
||||
folder, uid, target
|
||||
)));
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM imap WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
continue;
|
||||
}
|
||||
Err(async_imap::error::Error::No(text)) => {
|
||||
// "NO" response, probably the message is moved already.
|
||||
info!(
|
||||
context,
|
||||
"IMAP message {}/{} cannot be moved: {}", folder, uid, text
|
||||
);
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM imap WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}",
|
||||
folder,
|
||||
uid,
|
||||
target,
|
||||
err
|
||||
);
|
||||
}
|
||||
while uid_set.len() < 1000 {
|
||||
// Construct a new range.
|
||||
if let Some((start_rowid, start_uid, _)) =
|
||||
rows.next_if(|(_, _, start_target)| start_target == &target)
|
||||
{
|
||||
rowid_set.push(start_rowid);
|
||||
let mut end_uid = start_uid;
|
||||
|
||||
while let Some((next_rowid, next_uid, _)) =
|
||||
rows.next_if(|(_, next_uid, next_target)| {
|
||||
next_target == &target && *next_uid == end_uid + 1
|
||||
})
|
||||
{
|
||||
end_uid = next_uid;
|
||||
rowid_set.push(next_rowid);
|
||||
}
|
||||
|
||||
let uid_range = UidRange {
|
||||
start: start_uid,
|
||||
end: end_uid,
|
||||
};
|
||||
if !uid_set.is_empty() {
|
||||
uid_set.push(',');
|
||||
}
|
||||
uid_set.push_str(&uid_range.to_string());
|
||||
} else {
|
||||
bail!("No session while attempting to move the message");
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Server does not support MOVE, fallback to COPY/DELETE {}/{} to {}",
|
||||
folder,
|
||||
uid,
|
||||
target
|
||||
);
|
||||
}
|
||||
|
||||
// Server does not support MOVE or MOVE failed.
|
||||
// Copy the message to the destination folder and mark the record for deletion.
|
||||
if let Some(session) = &mut self.session {
|
||||
match session.uid_copy(&set, &target).await {
|
||||
Ok(_) => {
|
||||
context.emit_event(EventType::ImapMessageMoved(format!(
|
||||
"IMAP message {}/{} copied to {}",
|
||||
folder, uid, target
|
||||
)));
|
||||
// Plan deletion of the original message.
|
||||
context
|
||||
.sql
|
||||
.execute("UPDATE imap SET target='' WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
}
|
||||
Err(async_imap::error::Error::No(text)) => {
|
||||
// "NO" response, probably the message is moved already.
|
||||
info!(
|
||||
context,
|
||||
"IMAP message {}/{} cannot be copied: {}", folder, uid, text
|
||||
);
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM imap WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Could not copy message {}/{}: {}", folder, uid, err
|
||||
);
|
||||
// Break the loop to avoid moving messages out of order.
|
||||
// We can't proceed until this message is moved or copied.
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bail!("No session while attempting to copy the message");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes messages that are marked as planned for deletion in `imap` table.
|
||||
///
|
||||
/// This is the only place where messages are deleted from the IMAP server.
|
||||
async fn delete_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
|
||||
let rows = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, uid FROM imap
|
||||
WHERE folder=? AND target=''
|
||||
ORDER BY uid ASC
|
||||
LIMIT 50", // Do not try to delete too many messages at once.
|
||||
paramsv![folder],
|
||||
|row| {
|
||||
let rowid: i64 = row.get(0)?;
|
||||
let uid: u32 = row.get(1)?;
|
||||
Ok((rowid, uid))
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if rows.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (rowid, uid) in rows {
|
||||
match self.delete_msg(context, folder, uid).await {
|
||||
ImapActionResult::Failed | ImapActionResult::RetryLater => {
|
||||
warn!(context, "Deletion of message {}/{} failed", folder, uid);
|
||||
break;
|
||||
}
|
||||
ImapActionResult::Success => {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM imap WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty target folder name means messages should be deleted.
|
||||
if target.is_empty() {
|
||||
self.delete_message_batch(context, &uid_set, rowid_set)
|
||||
.await
|
||||
.with_context(|| format!("cannot delete batch of messages {:?}", &uid_set))?;
|
||||
} else {
|
||||
self.move_message_batch(context, &uid_set, rowid_set, &target)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"cannot move batch of messages {:?} to folder {:?}",
|
||||
&uid_set, target
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1283,7 +1310,8 @@ impl Imap {
|
||||
let folder = folder.clone();
|
||||
|
||||
// safe, as we checked above that there is a body.
|
||||
let body = body.unwrap();
|
||||
let body = body
|
||||
.context("we checked that message has body right above, but it has vanished")?;
|
||||
let is_seen = msg.flags().any(|flag| flag == Flag::Seen);
|
||||
|
||||
match dc_receive_imf_inner(
|
||||
@@ -1338,7 +1366,7 @@ impl Imap {
|
||||
bail!("Can't set flag, should reconnect");
|
||||
}
|
||||
|
||||
let session = self.session.as_mut().context("No session").unwrap();
|
||||
let session = self.session.as_mut().context("No session")?;
|
||||
let query = format!("+FLAGS ({})", flag);
|
||||
let mut responses = session
|
||||
.uid_store(uid_set, &query)
|
||||
@@ -1416,39 +1444,6 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_msg(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
uid: u32,
|
||||
) -> ImapActionResult {
|
||||
if let Some(imapresult) = self
|
||||
.prepare_imap_operation_on_msg(context, folder, uid)
|
||||
.await
|
||||
{
|
||||
return imapresult;
|
||||
}
|
||||
// we are connected, and the folder is selected
|
||||
|
||||
let display_imap_id = format!("{}/{}", folder, uid);
|
||||
|
||||
// mark the message for deletion
|
||||
if let Err(err) = self.add_flag_finalized(uid, "\\Deleted").await {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark message {} as \"Deleted\": {}.", display_imap_id, err
|
||||
);
|
||||
ImapActionResult::RetryLater
|
||||
} else {
|
||||
context.emit_event(EventType::ImapMessageDeleted(format!(
|
||||
"IMAP Message {} marked as deleted",
|
||||
display_imap_id
|
||||
)));
|
||||
self.config.selected_folder_needs_expunge = true;
|
||||
ImapActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ensure_configured_folders(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
@@ -1598,29 +1593,38 @@ impl Imap {
|
||||
self.config.can_check_quota
|
||||
}
|
||||
|
||||
pub async fn get_quota_roots(
|
||||
pub(crate) async fn get_quota_roots(
|
||||
&mut self,
|
||||
mailbox_name: &str,
|
||||
) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
let quota_roots = session.get_quota_root(mailbox_name).await?;
|
||||
Ok(quota_roots)
|
||||
} else {
|
||||
Err(anyhow!("Not connected to IMAP, no session"))
|
||||
}
|
||||
let session = self.session.as_mut().context("no session")?;
|
||||
let quota_roots = session.get_quota_root(mailbox_name).await?;
|
||||
Ok(quota_roots)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns target folder for a message found in the Spam folder.
|
||||
async fn spam_target_folder(
|
||||
async fn should_move_out_of_spam(
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
) -> Result<Option<Config>> {
|
||||
if let Some(chat) = prefetch_get_chat(context, headers).await? {
|
||||
if chat.blocked != Blocked::Not {
|
||||
) -> Result<bool> {
|
||||
if headers.get_header_value(HeaderDef::ChatVersion).is_some() {
|
||||
// If this is a chat message (i.e. has a ChatVersion header), then this might be
|
||||
// a securejoin message. We can't find out at this point as we didn't prefetch
|
||||
// the SecureJoin header. So, we always move chat messages out of Spam.
|
||||
// Two possibilities to change this would be:
|
||||
// 1. Remove the `&& !context.is_spam_folder(folder).await?` check from
|
||||
// `fetch_new_messages()`, and then let `dc_receive_imf()` check
|
||||
// if it's a spam message and should be hidden.
|
||||
// 2. Or add a flag to the ChatVersion header that this is a securejoin
|
||||
// request, and return `true` here only if the message has this flag.
|
||||
// `dc_receive_imf()` can then check if the securejoin request is valid.
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(msg) = get_prefetch_parent_message(context, headers).await? {
|
||||
if msg.chat_blocked != Blocked::Not {
|
||||
// Blocked or contact request message in the spam folder, leave it there.
|
||||
return Ok(None);
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
// No chat found.
|
||||
@@ -1628,22 +1632,38 @@ async fn spam_target_folder(
|
||||
from_field_to_contact_id(context, &mimeparser::get_from(headers), true).await?;
|
||||
if blocked_contact {
|
||||
// Contact is blocked, leave the message in spam.
|
||||
return Ok(None);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, from_id).await? {
|
||||
if chat_id_blocked.blocked != Blocked::Not {
|
||||
return Ok(None);
|
||||
return Ok(false);
|
||||
}
|
||||
} else if from_id != DC_CONTACT_ID_SELF {
|
||||
// No chat with this contact found.
|
||||
return Ok(None);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Returns target folder for a message found in the Spam folder.
|
||||
/// If this returns None, the message will not be moved out of the
|
||||
/// Spam folder, and as `fetch_new_messages()` doesn't download
|
||||
/// messages from the Spam folder, the message will be ignored.
|
||||
async fn spam_target_folder(
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
) -> Result<Option<Config>> {
|
||||
if !should_move_out_of_spam(context, headers).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if needs_move_to_mvbox(context, headers).await?
|
||||
// We don't want to move the message to the inbox or sentbox where we wouldn't
|
||||
// fetch it again:
|
||||
// If OnlyFetchMvbox is set, we don't want to move the message to
|
||||
// the inbox or sentbox where we wouldn't fetch it again:
|
||||
|| context.get_config_bool(Config::OnlyFetchMvbox).await?
|
||||
{
|
||||
Ok(Some(Config::ConfiguredMvboxFolder))
|
||||
@@ -2396,7 +2416,7 @@ mod tests {
|
||||
("Spam", true, true, "DeltaChat"),
|
||||
];
|
||||
|
||||
// These are the same as above, but all messages in Spam stay in Spam
|
||||
// These are the same as above, but non-chat messages in Spam stay in Spam
|
||||
const COMBINATIONS_REQUEST: &[(&str, bool, bool, &str)] = &[
|
||||
("INBOX", false, false, "INBOX"),
|
||||
("INBOX", false, true, "INBOX"),
|
||||
@@ -2407,9 +2427,9 @@ mod tests {
|
||||
("Sent", true, false, "Sent"),
|
||||
("Sent", true, true, "DeltaChat"),
|
||||
("Spam", false, false, "Spam"),
|
||||
("Spam", false, true, "Spam"),
|
||||
("Spam", false, true, "INBOX"),
|
||||
("Spam", true, false, "Spam"),
|
||||
("Spam", true, true, "Spam"),
|
||||
("Spam", true, true, "DeltaChat"),
|
||||
];
|
||||
|
||||
#[async_std::test]
|
||||
|
||||
@@ -197,7 +197,7 @@ impl Job {
|
||||
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
|
||||
paramsv![
|
||||
self.desired_timestamp,
|
||||
self.tries as i64,
|
||||
i64::from(self.tries),
|
||||
self.param.to_string(),
|
||||
self.job_id as i32,
|
||||
],
|
||||
@@ -676,7 +676,7 @@ fn get_backoff_time_offset(tries: u32, action: Action) -> i64 {
|
||||
if seconds < 1 {
|
||||
seconds = 1;
|
||||
}
|
||||
seconds as i64
|
||||
i64::from(seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
clippy::all,
|
||||
clippy::indexing_slicing,
|
||||
clippy::wildcard_imports,
|
||||
clippy::needless_borrow
|
||||
clippy::needless_borrow,
|
||||
clippy::cast_lossless
|
||||
)]
|
||||
#![allow(
|
||||
clippy::match_bool,
|
||||
|
||||
@@ -253,7 +253,8 @@ impl LoginParam {
|
||||
sql.set_raw_config(key, Some(&self.imap.server)).await?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_raw_config_int(key, self.imap.port as i32).await?;
|
||||
sql.set_raw_config_int(key, i32::from(self.imap.port))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_raw_config(key, Some(&self.imap.user)).await?;
|
||||
@@ -273,7 +274,8 @@ impl LoginParam {
|
||||
sql.set_raw_config(key, Some(&self.smtp.server)).await?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_raw_config_int(key, self.smtp.port as i32).await?;
|
||||
sql.set_raw_config_int(key, i32::from(self.smtp.port))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_raw_config(key, Some(&self.smtp.user)).await?;
|
||||
|
||||
@@ -183,7 +183,7 @@ impl rusqlite::types::ToSql for MsgId {
|
||||
format_err!("Invalid MsgId {}", self.0).into(),
|
||||
));
|
||||
}
|
||||
let val = rusqlite::types::Value::Integer(self.0 as i64);
|
||||
let val = rusqlite::types::Value::Integer(i64::from(self.0));
|
||||
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||
Ok(out)
|
||||
}
|
||||
@@ -194,7 +194,7 @@ impl rusqlite::types::FromSql for MsgId {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
// Would be nice if we could use match here, but alas.
|
||||
i64::column_result(value).and_then(|val| {
|
||||
if 0 <= val && val <= std::u32::MAX as i64 {
|
||||
if 0 <= val && val <= i64::from(std::u32::MAX) {
|
||||
Ok(MsgId::new(val as u32))
|
||||
} else {
|
||||
Err(rusqlite::types::FromSqlError::OutOfRange(val))
|
||||
|
||||
@@ -1203,6 +1203,9 @@ impl MimeMessage {
|
||||
if let Some(_disposition) = report_fields.get_header_value(HeaderDef::Disposition) {
|
||||
let original_message_id = report_fields
|
||||
.get_header_value(HeaderDef::OriginalMessageId)
|
||||
// MS Exchange doesn't add an Original-Message-Id header. Instead, they put
|
||||
// the original message id into the In-Reply-To header:
|
||||
.or_else(|| report.headers.get_header_value(HeaderDef::InReplyTo))
|
||||
.and_then(|v| parse_message_id(&v).ok());
|
||||
let additional_message_ids = report_fields
|
||||
.get_header_value(HeaderDef::AdditionalMessageIds)
|
||||
@@ -1480,8 +1483,8 @@ async fn update_gossip_peerstates(
|
||||
pub(crate) struct Report {
|
||||
/// Original-Message-ID header
|
||||
///
|
||||
/// It MUST be present if the original message has a Message-ID according to RFC 8098, but MS
|
||||
/// Exchange does not add it nevertheless, in which case it is `None`.
|
||||
/// It MUST be present if the original message has a Message-ID according to RFC 8098.
|
||||
/// In case we can't find it (shouldn't happen), this is None.
|
||||
original_message_id: Option<String>,
|
||||
/// Additional-Message-IDs
|
||||
additional_message_ids: Vec<String>,
|
||||
@@ -3183,10 +3186,32 @@ Message.
|
||||
#[async_std::test]
|
||||
async fn test_ms_exchange_mdn() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let raw =
|
||||
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||
|
||||
let original =
|
||||
include_bytes!("../test-data/message/ms_exchange_report_original_message.eml");
|
||||
dc_receive_imf(&t, original, "INBOX", false).await?;
|
||||
let original_msg_id = t.get_last_msg().await.id;
|
||||
|
||||
// 1. Test mimeparser directly
|
||||
let mdn =
|
||||
include_bytes!("../test-data/message/ms_exchange_report_disposition_notification.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, raw).await?;
|
||||
assert!(!mimeparser.mdn_reports.is_empty());
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, mdn).await?;
|
||||
assert_eq!(mimeparser.mdn_reports.len(), 1);
|
||||
assert_eq!(
|
||||
mimeparser.mdn_reports[0].original_message_id.as_deref(),
|
||||
Some("d5904dc344eeb5deaf9bb44603f0c716@posteo.de")
|
||||
);
|
||||
assert!(mimeparser.mdn_reports[0].additional_message_ids.is_empty());
|
||||
|
||||
// 2. Test that marking the original msg as read works
|
||||
dc_receive_imf(&t, mdn, "INBOX", false).await?;
|
||||
|
||||
assert_eq!(
|
||||
original_msg_id.get_state(&t).await?,
|
||||
MessageState::OutMdnRcvd
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
60
src/smtp.rs
60
src/smtp.rs
@@ -343,19 +343,48 @@ pub(crate) async fn send_msg_to_smtp(
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let (body, recipients, msg_id) = context
|
||||
// Increase retry count as soon as we have an SMTP connection. This ensures that the message is
|
||||
// eventually removed from the queue by exceeding retry limit even in case of an error that
|
||||
// keeps happening early in the message sending code, e.g. failure to read the message from the
|
||||
// database.
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE smtp SET retries=retries+1 WHERE id=?",
|
||||
paramsv![rowid],
|
||||
)
|
||||
.await
|
||||
.context("failed to update retries count")?;
|
||||
|
||||
let (body, recipients, msg_id, retries) = context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT mime, recipients, msg_id FROM smtp WHERE id=?",
|
||||
"SELECT mime, recipients, msg_id, retries FROM smtp WHERE id=?",
|
||||
paramsv![rowid],
|
||||
|row| {
|
||||
let mime: String = row.get(0)?;
|
||||
let recipients: String = row.get(1)?;
|
||||
let msg_id: MsgId = row.get(2)?;
|
||||
Ok((mime, recipients, msg_id))
|
||||
let retries: i64 = row.get(3)?;
|
||||
Ok((mime, recipients, msg_id, retries))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if retries > 6 {
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
msg_id,
|
||||
Some("Number of retries exceeded the limit."),
|
||||
)
|
||||
.await;
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM smtp WHERE id=?", paramsv![rowid])
|
||||
.await
|
||||
.context("failed to remove message with exceeded retry limit from smtp table")?;
|
||||
bail!("Number of retries exceeded the limit");
|
||||
}
|
||||
|
||||
let recipients_list = recipients
|
||||
.split(' ')
|
||||
.filter_map(
|
||||
@@ -421,27 +450,16 @@ pub(crate) async fn send_msg_to_smtp(
|
||||
};
|
||||
match status {
|
||||
Status::Finished(res) => {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM smtp WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
if res.is_ok() {
|
||||
msg_id.set_delivered(context).await?;
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM smtp WHERE id=?", paramsv![rowid])
|
||||
.await?;
|
||||
}
|
||||
res
|
||||
}
|
||||
Status::RetryNow | Status::RetryLater => {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE smtp SET retries=retries+1 WHERE id=?",
|
||||
paramsv![rowid],
|
||||
)
|
||||
.await
|
||||
.context("failed to update retries count")?;
|
||||
Err(format_err!("Retry"))
|
||||
}
|
||||
Status::RetryNow | Status::RetryLater => Err(format_err!("Retry")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,10 +472,6 @@ pub(crate) async fn send_smtp_messages(
|
||||
connection: &mut Smtp,
|
||||
) -> anyhow::Result<()> {
|
||||
context.send_sync_msg().await?; // Add sync message to the end of the queue if needed.
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM smtp WHERE retries > 5", paramsv![])
|
||||
.await?;
|
||||
let rowids = context
|
||||
.sql
|
||||
.query_map(
|
||||
|
||||
19
src/sql.rs
19
src/sql.rs
@@ -787,7 +787,7 @@ async fn maybe_add_from_param(
|
||||
async fn prune_tombstones(sql: &Sql) -> Result<()> {
|
||||
sql.execute(
|
||||
"DELETE FROM msgs
|
||||
WHERE (chat_id=? OR hidden)
|
||||
WHERE chat_id=?
|
||||
AND NOT EXISTS (
|
||||
SELECT * FROM imap WHERE msgs.rfc724_mid=rfc724_mid AND target!=''
|
||||
)",
|
||||
@@ -906,6 +906,23 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Regression test for a bug where housekeeping deleted drafts since their
|
||||
/// `hidden` flag is set.
|
||||
#[async_std::test]
|
||||
async fn test_housekeeping_dont_delete_drafts() {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
let chat = t.create_chat_with_contact("bob", "bob@example.com").await;
|
||||
let mut new_draft = Message::new(Viewtype::Text);
|
||||
new_draft.set_text(Some("This is my draft".to_string()));
|
||||
chat.id.set_draft(&t, Some(&mut new_draft)).await.unwrap();
|
||||
|
||||
housekeeping(&t).await.unwrap();
|
||||
|
||||
let loaded_draft = chat.id.get_draft(&t).await.unwrap();
|
||||
assert_eq!(loaded_draft.unwrap().text.unwrap(), "This is my draft");
|
||||
}
|
||||
|
||||
/// Regression test.
|
||||
///
|
||||
/// Previously the code checking for existence of `config` table
|
||||
|
||||
@@ -75,7 +75,7 @@ impl StatusUpdateId {
|
||||
|
||||
impl rusqlite::types::ToSql for StatusUpdateId {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let val = rusqlite::types::Value::Integer(self.0 as i64);
|
||||
let val = rusqlite::types::Value::Integer(i64::from(self.0));
|
||||
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
Return-Path: <anonymous@example.org>
|
||||
Return-Path: <bob@example.net>
|
||||
Delivered-To: anonymous@posteo.de
|
||||
Received: from proxy02.posteo.name ([127.0.0.1])
|
||||
by dovecot16.posteo.name (Dovecot) with LMTP id Cp2uFxP1sWHbCQEAchYRkQ
|
||||
for <anonymous@posteo.de>; Thu, 09 Dec 2021 13:25:38 +0100
|
||||
by dovecot16.posteo.name (Dovecot) with LMTP id GaxcARout2HxiwMAchYRkQ
|
||||
for <anonymous@posteo.de>; Mon, 13 Dec 2021 12:35:32 +0100
|
||||
Received: from proxy02.posteo.de ([127.0.0.1])
|
||||
by proxy02.posteo.name (Dovecot) with LMTP id MWsaCwrvsWG0wgEAGFAyLg
|
||||
; Thu, 09 Dec 2021 13:25:38 +0100
|
||||
Received: from mailin06.posteo.de (unknown [10.0.1.6])
|
||||
by proxy02.posteo.de (Postfix) with ESMTPS id 4J8tXy0KkMz120l
|
||||
for <anonymous@posteo.de>; Thu, 9 Dec 2021 13:25:38 +0100 (CET)
|
||||
Received: from mx04.posteo.de (mailin06.posteo.de [127.0.0.1])
|
||||
by mailin06.posteo.de (Postfix) with ESMTPS id F24DE215B8
|
||||
for <anonymous@posteo.de>; Thu, 9 Dec 2021 13:25:37 +0100 (CET)
|
||||
by proxy02.posteo.name (Dovecot) with LMTP id q/LiCqwqt2FMTQEAGFAyLg
|
||||
; Mon, 13 Dec 2021 12:35:32 +0100
|
||||
Received: from mailin05.posteo.de (unknown [10.0.1.5])
|
||||
by proxy02.posteo.de (Postfix) with ESMTPS id 4JCKFJ1LCLz1214
|
||||
for <anonymous@posteo.de>; Mon, 13 Dec 2021 12:35:32 +0100 (CET)
|
||||
Received: from mx03.posteo.de (mailin05.posteo.de [127.0.0.1])
|
||||
by mailin05.posteo.de (Postfix) with ESMTPS id 1B26420012
|
||||
for <anonymous@posteo.de>; Mon, 13 Dec 2021 12:35:32 +0100 (CET)
|
||||
X-Virus-Scanned: amavisd-new at posteo.de
|
||||
X-Spam-Flag: NO
|
||||
X-Spam-Score: 0.011
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=0.011 tagged_above=-1000 required=7
|
||||
tests=[HTML_MESSAGE=0.001, T_POSTEO_TLSINY=0.01] autolearn=disabled
|
||||
X-Posteo-Antispam-Signature: v=1; e=base64; a=aes-256-gcm; d=27yedFdXeAzOobR4x685XJ/5e6WQmX8PP5pSnOlGU2a9Ismhk38wb5AS44xh1yeL5PUxla78UEsHwGkPR0IyPRlHWaLMFLd5CJZN3GzFfrj/2CuB+cd1hOLpp9hRmCebc3rchuDr
|
||||
X-Posteo-Antispam-Signature: v=1; e=base64; a=aes-256-gcm; d=RzB41PpqvrD+cuxf3UAqQLiXQL4MHazHZcKeOYJw75deIl7zxtrLXqfAZCeq2IPKt/njRRONUbfuvNdvLxg4mBJ0Rnb53wFOKOqtEpTzxYoQff3yqBpGSohr0DBG26PyBHi7ba/7
|
||||
Authentication-Results: posteo.de; dmarc=none (p=none dis=none) header.from=example.org
|
||||
X-Posteo-TLS-Received-Status: TLSv1.2
|
||||
Received: from mail.example.org (mail.example.org [0.0.0.0])
|
||||
by mx04.posteo.de (Postfix) with ESMTPS id 4J8tXx38vRz10yw
|
||||
for <anonymous@posteo.at>; Thu, 9 Dec 2021 13:25:37 +0100 (CET)
|
||||
Received: from [192.168.1.11] (port=22105 helo=mail.example.org)
|
||||
by mx03.posteo.de (Postfix) with ESMTPS id 4JCKFH2ZM7zyx0
|
||||
for <alice@example.org>; Mon, 13 Dec 2021 12:35:31 +0100 (CET)
|
||||
Received: from [192.168.1.11] (port=27040 helo=mail.example.org)
|
||||
by mail.example.org with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
|
||||
(Exim 4.94.2)
|
||||
(envelope-from <anonymous@example.org>)
|
||||
id 1mvIUG-0007VC-2U
|
||||
for anonymous@posteo.at; Thu, 09 Dec 2021 13:25:24 +0100
|
||||
From: Anonymous <anonymous@example.org>
|
||||
To: Anonymous <anonymous@posteo.at>
|
||||
(envelope-from <bob@example.net>)
|
||||
id 1mwjc6-0003hM-2K
|
||||
for alice@example.org; Mon, 13 Dec 2021 12:35:26 +0100
|
||||
From: Anonymous_2 <bob@example.net>
|
||||
To: Anonymous_1 <alice@example.org>
|
||||
Subject: Gelesen: Test message
|
||||
Thread-Topic: Test message
|
||||
Thread-Index: AQHX7Dt/+5f88Aokk0KrqG0hbF8dN6wqFvxh
|
||||
Date: Thu, 9 Dec 2021 12:25:24 +0000
|
||||
Message-ID: <1711fc3548cd4b2699ccd4fffac17713@anonymous>
|
||||
In-Reply-To: <75dd051097b02468183707ad0dd62ebd@posteo.de>
|
||||
Thread-Index: AQHX8BVZ9B3+kB6CNUCtJ9eQIONSNawwSqpi
|
||||
Date: Mon, 13 Dec 2021 11:35:26 +0000
|
||||
Message-ID: <59b1d0c94a8d4834b7ab779a76647d44@mail.example.org>
|
||||
In-Reply-To: <d5904dc344eeb5deaf9bb44603f0c716@posteo.de>
|
||||
Accept-Language: de-AT, de-DE, en-US
|
||||
Content-Language: de-DE
|
||||
X-MS-Has-Attach:
|
||||
@@ -45,29 +45,29 @@ X-MS-TNEF-Correlator:
|
||||
x-ms-exchange-transport-fromentityheader: Hosted
|
||||
x-originating-ip: [192.168.120.215]
|
||||
Content-Type: multipart/report;
|
||||
boundary="_000_1711fc3548cd4b2699ccd4fffac17713anonymous_";
|
||||
boundary="_000_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_";
|
||||
report-type=disposition-notification
|
||||
MIME-Version: 1.0
|
||||
|
||||
--_000_1711fc3548cd4b2699ccd4fffac17713anonymous_
|
||||
--_000_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="_002_1711fc3548cd4b2699ccd4fffac17713anonymous_"
|
||||
boundary="_002_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_"
|
||||
|
||||
--_002_1711fc3548cd4b2699ccd4fffac17713anonymous_
|
||||
--_002_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_
|
||||
Content-Type: text/plain; charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Ihre Nachricht
|
||||
|
||||
An: Anonymous
|
||||
An: Anonymous_2
|
||||
Betreff: Test message
|
||||
Gesendet: Mittwoch, 8. Dezember 2021 14:57:05 (UTC+01:00) Amsterdam, Ber=
|
||||
lin, Bern, Rom, Stockholm, Wien
|
||||
Gesendet: Montag, 13. Dezember 2021 12:33:58 (UTC+01:00) Amsterdam, Berl=
|
||||
in, Bern, Rom, Stockholm, Wien
|
||||
|
||||
wurde am Donnerstag, 9. Dezember 2021 13:24:34 (UTC+01:00) Amsterdam, Berl=
|
||||
in, Bern, Rom, Stockholm, Wien gelesen.
|
||||
wurde am Montag, 13. Dezember 2021 12:34:40 (UTC+01:00) Amsterdam, Berlin,=
|
||||
Bern, Rom, Stockholm, Wien gelesen.
|
||||
|
||||
--_002_1711fc3548cd4b2699ccd4fffac17713anonymous_
|
||||
--_002_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_
|
||||
Content-Type: text/html; charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
@@ -83,26 +83,26 @@ ding-left: 4pt; border-left: #800000 2px solid; } --></style>
|
||||
<font size=3D"2"><span style=3D"font-size:10pt;">
|
||||
<div class=3D"PlainText">Ihre Nachricht <br>
|
||||
<br>
|
||||
An: Anonymous<br>
|
||||
An: Anonymous_2<br>
|
||||
Betreff: Test message<br>
|
||||
Gesendet: Mittwoch, 8. Dezember 2021 14:57:05 (UTC+01:00) =
|
||||
Amsterdam, Berlin, Bern, Rom, Stockholm, Wien<br>
|
||||
Gesendet: Montag, 13. Dezember 2021 12:33:58 (UTC+01:00) A=
|
||||
msterdam, Berlin, Bern, Rom, Stockholm, Wien<br>
|
||||
<br>
|
||||
wurde am Donnerstag, 9. Dezember 2021 13:24:34 (UTC+01:00) Amster=
|
||||
dam, Berlin, Bern, Rom, Stockholm, Wien gelesen.</div>
|
||||
wurde am Montag, 13. Dezember 2021 12:34:40 (UTC+01:00) Amsterdam=
|
||||
, Berlin, Bern, Rom, Stockholm, Wien gelesen.</div>
|
||||
</span></font>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
--_002_1711fc3548cd4b2699ccd4fffac17713anonymous_--
|
||||
--_002_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_--
|
||||
|
||||
--_000_1711fc3548cd4b2699ccd4fffac17713anonymous_
|
||||
--_000_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_
|
||||
Content-Type: message/disposition-notification
|
||||
|
||||
Final-recipient: RFC822; anonymous@example.org
|
||||
Final-recipient: RFC822; bob@example.net
|
||||
Disposition: automatic-action/MDN-sent-automatically; displayed
|
||||
X-MSExch-Correlation-Key: coNC5vaCQkiAOjek1v1Uew==
|
||||
X-Display-Name: Anonymous
|
||||
X-MSExch-Correlation-Key: nf7/jgN6Qk+WzsrkY5s9WA==
|
||||
X-Display-Name: Anonymous_2
|
||||
|
||||
|
||||
--_000_1711fc3548cd4b2699ccd4fffac17713anonymous_--
|
||||
--_000_59b1d0c94a8d4834b7ab779a76647d44mailexampleorg_--
|
||||
|
||||
34
test-data/message/ms_exchange_report_original_message.eml
Normal file
34
test-data/message/ms_exchange_report_original_message.eml
Normal file
@@ -0,0 +1,34 @@
|
||||
Received: We have to put a Received header here. Otherwise, the message would be ignored
|
||||
because DC thinks it's a draft, and the test fails.
|
||||
Alternatively, we could configure the Sentobox folder in the test.
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="=_3293d145f71bf71c4bb97415536759d6"
|
||||
Date: Mon, 13 Dec 2021 12:33:58 +0100
|
||||
From: Anonymous_1 <alice@example.org>
|
||||
To: Anonymous_2 <bob@example.net>
|
||||
Subject: Test message
|
||||
Return-Receipt-To: Anonymous_1 <alice@example.org>
|
||||
Disposition-Notification-To: Anonymous_1 <alice@example.org>
|
||||
Message-ID: <d5904dc344eeb5deaf9bb44603f0c716@posteo.de>
|
||||
X-Sender: alice@example.org
|
||||
User-Agent: Posteo Webmail
|
||||
|
||||
--=_3293d145f71bf71c4bb97415536759d6
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Type: text/plain; charset=US-ASCII
|
||||
|
||||
This is a test!
|
||||
|
||||
Best regards
|
||||
--=_3293d145f71bf71c4bb97415536759d6
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
|
||||
<html><body style=3D'font-size: 10pt; font-family: Verdana,Geneva,sans-seri=
|
||||
f'>
|
||||
This is a test!<br /><br />Best regards
|
||||
</body></html>
|
||||
|
||||
--=_3293d145f71bf71c4bb97415536759d6--
|
||||
Reference in New Issue
Block a user