mirror of
https://github.com/chatmail/core.git
synced 2026-10-28 15:58:22 +03:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3010d28901 | ||
|
|
e3973f6448 | ||
|
|
7b41425fe4 | ||
|
|
2c7d51f98f |
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## [2.3.0] - 2025-07-19
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add "e2ee encrypted" info message to all e2ee chats ([#7008](https://github.com/chatmail/core/pull/7008)).
|
||||
- repl: Print errors and debug logs to stderr.
|
||||
- `{ensure_and,logged}_debug_assert`: Don't evaluate condition twice.
|
||||
- Log when background fetch of all accounts finishes successfully.
|
||||
- Log the number of read/written bytes on IMAP stream read error ([#6924](https://github.com/chatmail/core/pull/6924)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Ignore protected headers in outer message part ([#6357](https://github.com/chatmail/core/pull/6357)).
|
||||
- List e-mail contacts in repl listcontacts command.
|
||||
- Save peer address for LoggingStream early.
|
||||
|
||||
## [2.2.0] - 2025-07-14
|
||||
|
||||
### API-Changes
|
||||
@@ -6478,3 +6494,4 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[2.0.0]: https://github.com/chatmail/core/compare/v1.160.0..v2.0.0
|
||||
[2.1.0]: https://github.com/chatmail/core/compare/v2.0.0..v2.1.0
|
||||
[2.2.0]: https://github.com/chatmail/core/compare/v2.1.0..v2.2.0
|
||||
[2.3.0]: https://github.com/chatmail/core/compare/v2.2.0..v2.3.0
|
||||
|
||||
Generated
+5
-5
@@ -1285,7 +1285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
@@ -1395,7 +1395,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.3.1",
|
||||
@@ -1417,7 +1417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1433,7 +1433,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1462,7 +1462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.85"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -4535,12 +4535,12 @@ int dc_msg_is_info (const dc_msg_t* msg);
|
||||
* - DC_INFO_MEMBER_ADDED_TO_GROUP (4) - "Member CONTACT added by OTHER_CONTACT"
|
||||
* - DC_INFO_MEMBER_REMOVED_FROM_GROUP (5) - "Member CONTACT removed by OTHER_CONTACT"
|
||||
* - DC_INFO_EPHEMERAL_TIMER_CHANGED (10) - "Disappearing messages CHANGED_TO by CONTACT"
|
||||
* - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is now protected"
|
||||
* - DC_INFO_PROTECTION_DISABLED (12) - Info-message for "Chat is no longer protected"
|
||||
* - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is protected"
|
||||
* - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet",
|
||||
* the UI should change the corresponding string using #DC_STR_INVALID_UNENCRYPTED_MAIL
|
||||
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
|
||||
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
|
||||
* - DC_INFO_CHAT_E2EE (50) - Info-message for "Chat is end-to-end-encrypted"
|
||||
*
|
||||
* For the messages that refer to a CONTACT,
|
||||
* dc_msg_get_info_contact_id() returns the contact ID.
|
||||
@@ -4593,9 +4593,10 @@ uint32_t dc_msg_get_info_contact_id (const dc_msg_t* msg);
|
||||
#define DC_INFO_LOCATION_ONLY 9
|
||||
#define DC_INFO_EPHEMERAL_TIMER_CHANGED 10
|
||||
#define DC_INFO_PROTECTION_ENABLED 11
|
||||
#define DC_INFO_PROTECTION_DISABLED 12
|
||||
#define DC_INFO_PROTECTION_DISABLED 12 // deprecated 2025-07
|
||||
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
|
||||
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
|
||||
#define DC_INFO_CHAT_E2EE 50
|
||||
|
||||
|
||||
/**
|
||||
@@ -6898,9 +6899,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used in summaries.
|
||||
#define DC_STR_GIF 23
|
||||
|
||||
/// "Encrypted message"
|
||||
///
|
||||
/// Used in subjects of outgoing messages.
|
||||
/// @deprecated 2025-07, this string is no longer needed.
|
||||
#define DC_STR_ENCRYPTEDMSG 24
|
||||
|
||||
/// "End-to-end encryption available."
|
||||
@@ -7605,7 +7604,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used as a device message after a successful backup transfer.
|
||||
#define DC_STR_BACKUP_TRANSFER_MSG_BODY 163
|
||||
|
||||
/// "Messages are guaranteed to be end-to-end encrypted from now on."
|
||||
/// "Messages are end-to-end encrypted."
|
||||
///
|
||||
/// Used in info messages.
|
||||
#define DC_STR_CHAT_PROTECTION_ENABLED 170
|
||||
@@ -7613,6 +7612,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// "%1$s sent a message from another device."
|
||||
///
|
||||
/// Used in info messages.
|
||||
/// @deprecated 2025-07
|
||||
#define DC_STR_CHAT_PROTECTION_DISABLED 171
|
||||
|
||||
/// "Others will only see this group after you sent a first message."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -416,6 +416,9 @@ pub enum SystemMessageType {
|
||||
/// Chat ephemeral message timer is changed.
|
||||
EphemeralTimerChanged,
|
||||
|
||||
// Chat is e2ee
|
||||
ChatE2ee,
|
||||
|
||||
// Chat protection state changed
|
||||
ChatProtectionEnabled,
|
||||
ChatProtectionDisabled,
|
||||
@@ -450,6 +453,7 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
SystemMessage::LocationStreamingEnabled => SystemMessageType::LocationStreamingEnabled,
|
||||
SystemMessage::LocationOnly => SystemMessageType::LocationOnly,
|
||||
SystemMessage::EphemeralTimerChanged => SystemMessageType::EphemeralTimerChanged,
|
||||
SystemMessage::ChatE2ee => SystemMessageType::ChatE2ee,
|
||||
SystemMessage::ChatProtectionEnabled => SystemMessageType::ChatProtectionEnabled,
|
||||
SystemMessage::ChatProtectionDisabled => SystemMessageType::ChatProtectionDisabled,
|
||||
SystemMessage::MultiDeviceSync => SystemMessageType::MultiDeviceSync,
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.2.0"
|
||||
"version": "2.3.0"
|
||||
}
|
||||
|
||||
@@ -95,8 +95,10 @@ describe("online tests", function () {
|
||||
false,
|
||||
);
|
||||
|
||||
expect(messageList).have.length(1);
|
||||
const message = await dc.rpc.getMessage(accountId2, messageList[0]);
|
||||
// There are 2 messages in the chat:
|
||||
// 'Messages are end-to-end encrypted' (info message) and 'Hello'
|
||||
expect(messageList).have.length(2);
|
||||
const message = await dc.rpc.getMessage(accountId2, messageList[1]);
|
||||
expect(message.text).equal("Hello");
|
||||
expect(message.showPadlock).equal(true);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -13,6 +13,12 @@ from . import Account, AttrDict, Bot, Chat, Client, DeltaChat, EventType, Messag
|
||||
from ._utils import futuremethod
|
||||
from .rpc import Rpc
|
||||
|
||||
E2EE_INFO_MSGS = 1
|
||||
"""
|
||||
The number of info messages added to new e2ee chats.
|
||||
Currently this is "End-to-end encryption available".
|
||||
"""
|
||||
|
||||
|
||||
class ACFactory:
|
||||
"""Test account factory."""
|
||||
|
||||
@@ -36,6 +36,9 @@ def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
|
||||
# Second client receives only second message, but not the first.
|
||||
ev_msg = ac1_clone.wait_for_event(EventType.MSGS_CHANGED)
|
||||
assert ac1_clone.get_message_by_id(ev_msg.msg_id).get_snapshot().text == "Messages are end-to-end encrypted."
|
||||
|
||||
ev_msg = ac1_clone.wait_for_event(EventType.MSGS_CHANGED)
|
||||
assert ac1_clone.get_message_by_id(ev_msg.msg_id).get_snapshot().text == msg_out.get_snapshot().text
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import pytest
|
||||
|
||||
from deltachat_rpc_client import Contact, EventType, Message, events
|
||||
from deltachat_rpc_client.const import ChatType, DownloadState, MessageState
|
||||
from deltachat_rpc_client.pytestplugin import E2EE_INFO_MSGS
|
||||
from deltachat_rpc_client.rpc import JsonRpcError
|
||||
|
||||
|
||||
@@ -457,8 +458,12 @@ def test_wait_next_messages(acfactory) -> None:
|
||||
alice_chat_bot.send_text("Hello!")
|
||||
|
||||
next_messages = next_messages_task.result()
|
||||
assert len(next_messages) == 1
|
||||
snapshot = next_messages[0].get_snapshot()
|
||||
|
||||
if len(next_messages) == E2EE_INFO_MSGS:
|
||||
next_messages += bot.wait_next_messages()
|
||||
|
||||
assert len(next_messages) == 1 + E2EE_INFO_MSGS
|
||||
snapshot = next_messages[0 + E2EE_INFO_MSGS].get_snapshot()
|
||||
assert snapshot.text == "Hello!"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.2.0"
|
||||
"version": "2.3.0"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.8"
|
||||
|
||||
@@ -20,6 +20,12 @@ import deltachat
|
||||
from . import Account, account_hookimpl, const, get_core_info
|
||||
from .events import FFIEventLogger, FFIEventTracker
|
||||
|
||||
E2EE_INFO_MSGS = 1
|
||||
"""
|
||||
The number of info messages added to new e2ee chats.
|
||||
Currently this is "End-to-end encryption available".
|
||||
"""
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("deltachat testplugin options")
|
||||
@@ -606,7 +612,7 @@ class ACFactory:
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev.data2)
|
||||
assert msg is not None
|
||||
assert msg.text == "Messages are guaranteed to be end-to-end encrypted from now on."
|
||||
assert msg.text == "Messages are end-to-end encrypted."
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg is not None
|
||||
assert "Member Me " in msg.text and " added by " in msg.text
|
||||
|
||||
@@ -133,8 +133,7 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
|
||||
assert "added" in msg.text.lower()
|
||||
|
||||
assert any(
|
||||
m.is_system_message() and m.text == "Messages are guaranteed to be end-to-end encrypted from now on."
|
||||
for m in msg.chat.get_messages()
|
||||
m.is_system_message() and m.text == "Messages are end-to-end encrypted." for m in msg.chat.get_messages()
|
||||
)
|
||||
lp.sec("ac1: send message")
|
||||
msg_out = chat1.send_text("hello")
|
||||
@@ -338,7 +337,7 @@ def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp
|
||||
assert contact.addr == ac1.get_config("addr")
|
||||
chat2 = msg_in.chat
|
||||
assert chat2.is_protected()
|
||||
assert chat2.get_messages()[0].text == "Messages are guaranteed to be end-to-end encrypted from now on."
|
||||
assert chat2.get_messages()[0].text == "Messages are end-to-end encrypted."
|
||||
assert open(contact.get_profile_image(), "rb").read() == open(avatar_path, "rb").read()
|
||||
|
||||
lp.sec("ac2_offl: sending message")
|
||||
@@ -412,7 +411,7 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
|
||||
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2_offl.get_message_by_id(ev.data2)
|
||||
assert msg_in.is_system_message()
|
||||
assert msg_in.text == "Messages are guaranteed to be end-to-end encrypted from now on."
|
||||
assert msg_in.text == "Messages are end-to-end encrypted."
|
||||
|
||||
# We need to consume one event that has data2=0
|
||||
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
|
||||
@@ -10,6 +10,7 @@ from imap_tools import AND, U
|
||||
import deltachat as dc
|
||||
from deltachat import account_hookimpl, Message
|
||||
from deltachat.tracker import ImexTracker
|
||||
from deltachat.testplugin import E2EE_INFO_MSGS
|
||||
|
||||
|
||||
def test_basic_imap_api(acfactory, tmp_path):
|
||||
@@ -408,6 +409,10 @@ def test_forward_messages(acfactory, lp):
|
||||
msg_out = chat.send_text("message2")
|
||||
|
||||
lp.sec("ac2: wait for receive")
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg_in = ac2.get_message_by_id(ev.data2)
|
||||
assert msg_in.text == "Messages are end-to-end encrypted."
|
||||
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
assert ev.data2 == msg_out.id
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
@@ -622,6 +627,11 @@ def test_moved_markseen(acfactory):
|
||||
|
||||
with ac2.direct_imap.idle() as idle2:
|
||||
ac2.start_io()
|
||||
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev.data2)
|
||||
assert msg.text == "Messages are end-to-end encrypted."
|
||||
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||
msg = ac2.get_message_by_id(ev.data2)
|
||||
|
||||
@@ -738,7 +748,7 @@ def test_mdn_asymmetric(acfactory, lp):
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg_out = chat.send_text("message1")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
|
||||
|
||||
lp.sec("disable ac1 MDNs")
|
||||
ac1.set_config("mdns_enabled", "0")
|
||||
@@ -746,7 +756,7 @@ def test_mdn_asymmetric(acfactory, lp):
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
|
||||
assert len(msg.chat.get_messages()) == 1
|
||||
assert len(msg.chat.get_messages()) == 1 + E2EE_INFO_MSGS
|
||||
|
||||
lp.sec("ac2: mark incoming message as seen")
|
||||
ac2.mark_seen_messages([msg])
|
||||
@@ -755,7 +765,7 @@ def test_mdn_asymmetric(acfactory, lp):
|
||||
# MDN should be moved even though MDNs are already disabled
|
||||
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
|
||||
|
||||
assert len(chat.get_messages()) == 1
|
||||
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
|
||||
|
||||
# Wait for the message to be marked as seen on IMAP.
|
||||
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
|
||||
@@ -1123,6 +1133,11 @@ def test_send_and_receive_image(acfactory, lp, data):
|
||||
assert m == msg_out
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED|DC_EVENT_INCOMING_MSG")
|
||||
msg_in = ac2.get_message_by_id(ev.data2)
|
||||
assert msg_in.text == "Messages are end-to-end encrypted."
|
||||
|
||||
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED|DC_EVENT_INCOMING_MSG")
|
||||
assert ev.data2 == msg_out.id
|
||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||
@@ -1158,10 +1173,10 @@ def test_import_export_online_all(acfactory, tmp_path, data, lp):
|
||||
assert contact2.addr == some1_addr
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 3
|
||||
assert messages[0].text == "msg1"
|
||||
assert messages[1].filemime == "image/png"
|
||||
assert os.stat(messages[1].filename).st_size == os.stat(original_image_path).st_size
|
||||
assert len(messages) == 3 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert messages[1 + E2EE_INFO_MSGS].filemime == "image/png"
|
||||
assert os.stat(messages[1 + E2EE_INFO_MSGS].filename).st_size == os.stat(original_image_path).st_size
|
||||
ac.set_config("displayname", "new displayname")
|
||||
assert ac.get_config("displayname") == "new displayname"
|
||||
|
||||
@@ -1414,8 +1429,8 @@ def test_connectivity(acfactory, lp):
|
||||
ac1.maybe_network()
|
||||
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTED)
|
||||
msgs = ac1.create_chat(ac2).get_messages()
|
||||
assert len(msgs) == 1
|
||||
assert msgs[0].text == "Hi"
|
||||
assert len(msgs) == 1 + E2EE_INFO_MSGS
|
||||
assert msgs[0 + E2EE_INFO_MSGS].text == "Hi"
|
||||
|
||||
lp.sec("Test that the connectivity changes to WORKING while new messages are fetched")
|
||||
|
||||
@@ -1425,8 +1440,8 @@ def test_connectivity(acfactory, lp):
|
||||
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_WORKING, dc.const.DC_CONNECTIVITY_CONNECTED)
|
||||
|
||||
msgs = ac1.create_chat(ac2).get_messages()
|
||||
assert len(msgs) == 2
|
||||
assert msgs[1].text == "Hi 2"
|
||||
assert len(msgs) == 2 + E2EE_INFO_MSGS
|
||||
assert msgs[1 + E2EE_INFO_MSGS].text == "Hi 2"
|
||||
|
||||
|
||||
def test_fetch_deleted_msg(acfactory, lp):
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
import deltachat as dc
|
||||
from deltachat.tracker import ImexFailed
|
||||
from deltachat import Account, Message
|
||||
from deltachat.testplugin import E2EE_INFO_MSGS
|
||||
|
||||
|
||||
class TestOfflineAccountBasic:
|
||||
@@ -461,9 +462,9 @@ class TestOfflineChat:
|
||||
assert contact2.addr == ac_contact.get_config("addr")
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 2
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
assert len(messages) == 2 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
|
||||
|
||||
def test_import_export_on_encrypted_acct(self, acfactory, tmp_path):
|
||||
passphrase1 = "passphrase1"
|
||||
@@ -500,9 +501,9 @@ class TestOfflineChat:
|
||||
contact2_addr = contact2.addr
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 2
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
assert len(messages) == 2 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
|
||||
|
||||
ac2.shutdown()
|
||||
|
||||
@@ -517,9 +518,9 @@ class TestOfflineChat:
|
||||
assert contact2.addr == contact2_addr
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 2
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
assert len(messages) == 2 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
|
||||
|
||||
def test_import_export_with_passphrase(self, acfactory, tmp_path):
|
||||
passphrase = "test_passphrase"
|
||||
@@ -557,9 +558,9 @@ class TestOfflineChat:
|
||||
assert contact2.addr == ac_contact.get_config("addr")
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 2
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
assert len(messages) == 2 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
|
||||
|
||||
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmp_path):
|
||||
"""
|
||||
@@ -603,9 +604,9 @@ class TestOfflineChat:
|
||||
assert contact2.addr == ac_contact.get_config("addr")
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 2
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
assert len(messages) == 2 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
|
||||
|
||||
ac2.shutdown()
|
||||
|
||||
@@ -620,9 +621,9 @@ class TestOfflineChat:
|
||||
assert contact2.addr == ac_contact.get_config("addr")
|
||||
chat2 = contact2.create_chat()
|
||||
messages = chat2.get_messages()
|
||||
assert len(messages) == 2
|
||||
assert messages[0].text == "msg1"
|
||||
assert os.path.exists(messages[1].filename)
|
||||
assert len(messages) == 2 + E2EE_INFO_MSGS
|
||||
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
|
||||
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
|
||||
|
||||
def test_set_get_draft(self, chat1):
|
||||
msg1 = Message.new_empty(chat1.account, "text")
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2025-07-14
|
||||
2025-07-19
|
||||
+42
@@ -349,6 +349,8 @@ impl ChatId {
|
||||
chat_id
|
||||
.add_protection_msg(context, ProtectionStatus::Protected, None, timestamp)
|
||||
.await?;
|
||||
} else {
|
||||
chat_id.maybe_add_encrypted_msg(context, timestamp).await?;
|
||||
}
|
||||
|
||||
info!(
|
||||
@@ -604,6 +606,42 @@ impl ChatId {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds message "Messages are end-to-end encrypted" if appropriate.
|
||||
///
|
||||
/// This function is rather slow because it does a lot of database queries,
|
||||
/// but this is fine because it is only called on chat creation.
|
||||
async fn maybe_add_encrypted_msg(self, context: &Context, timestamp_sort: i64) -> Result<()> {
|
||||
let chat = Chat::load_from_db(context, self).await?;
|
||||
|
||||
// as secure-join adds its own message on success (after some other messasges),
|
||||
// we do not want to add "Messages are end-to-end encrypted" on chat creation.
|
||||
// we detect secure join by `can_send` (for Bob, scanner side) and by `blocked` (for Alice, inviter side) below.
|
||||
if !chat.is_encrypted(context).await?
|
||||
|| self <= DC_CHAT_ID_LAST_SPECIAL
|
||||
|| chat.is_device_talk()
|
||||
|| chat.is_self_talk()
|
||||
|| (!chat.can_send(context).await? && !chat.is_contact_request())
|
||||
|| chat.blocked == Blocked::Yes
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let text = stock_str::messages_e2e_encrypted(context).await;
|
||||
add_info_msg_with_cmd(
|
||||
context,
|
||||
self,
|
||||
&text,
|
||||
SystemMessage::ChatE2ee,
|
||||
timestamp_sort,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets protection and adds a message.
|
||||
///
|
||||
/// `timestamp_sort` is used as the timestamp of the added message
|
||||
@@ -2673,6 +2711,10 @@ impl ChatIdBlocked {
|
||||
smeared_time,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
chat_id
|
||||
.maybe_add_encrypted_msg(context, smeared_time)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
||||
+34
-16
@@ -8,7 +8,7 @@ use crate::message::{MessengerMessage, delete_msgs};
|
||||
use crate::mimeparser::{self, MimeMessage};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{
|
||||
AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext, TestContextManager,
|
||||
AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, E2EE_INFO_MSGS, TestContext, TestContextManager,
|
||||
TimeShiftFalsePositiveNote, sync,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -2104,7 +2104,7 @@ async fn test_forward_basic() -> Result<()> {
|
||||
forward_msgs(&bob, &[msg.id], bob_chat.get_id()).await?;
|
||||
|
||||
let forwarded_msg = bob.pop_sent_msg().await;
|
||||
assert_eq!(bob_chat.id.get_msg_cnt(&bob).await?, 2);
|
||||
assert_eq!(bob_chat.id.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 2);
|
||||
assert_ne!(
|
||||
forwarded_msg.load_from_db().await.rfc724_mid,
|
||||
msg.rfc724_mid,
|
||||
@@ -2132,7 +2132,7 @@ async fn test_forward_info_msg() -> Result<()> {
|
||||
assert!(msg1.get_text().contains("bob@example.net"));
|
||||
|
||||
let chat_id2 = ChatId::create_for_contact(alice, bob_id).await?;
|
||||
assert_eq!(get_chat_msgs(alice, chat_id2).await?.len(), 0);
|
||||
assert_eq!(get_chat_msgs(alice, chat_id2).await?.len(), E2EE_INFO_MSGS);
|
||||
forward_msgs(alice, &[msg1.id], chat_id2).await?;
|
||||
let msg2 = alice.get_last_msg_in(chat_id2).await;
|
||||
assert!(!msg2.is_info()); // forwarded info-messages lose their info-state
|
||||
@@ -2518,22 +2518,34 @@ async fn test_resend_own_message() -> Result<()> {
|
||||
let sent1_ts_sent = msg.timestamp_sent;
|
||||
assert_eq!(msg.get_text(), "alice->bob");
|
||||
assert_eq!(get_chat_contacts(&bob, msg.chat_id).await?.len(), 2);
|
||||
assert_eq!(get_chat_msgs(&bob, msg.chat_id).await?.len(), 1);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 1
|
||||
);
|
||||
bob.recv_msg(&sent2).await;
|
||||
assert_eq!(get_chat_contacts(&bob, msg.chat_id).await?.len(), 3);
|
||||
assert_eq!(get_chat_msgs(&bob, msg.chat_id).await?.len(), 2);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
let received = bob.recv_msg_opt(&sent3).await;
|
||||
// No message should actually be added since we already know this message:
|
||||
assert!(received.is_none());
|
||||
assert_eq!(get_chat_contacts(&bob, msg.chat_id).await?.len(), 3);
|
||||
assert_eq!(get_chat_msgs(&bob, msg.chat_id).await?.len(), 2);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
|
||||
// Fiona does not receive the first message, however, due to resending, she has a similar view as Alice and Bob
|
||||
fiona.recv_msg(&sent2).await;
|
||||
let msg = fiona.recv_msg(&sent3).await;
|
||||
assert_eq!(msg.get_text(), "alice->bob");
|
||||
assert_eq!(get_chat_contacts(&fiona, msg.chat_id).await?.len(), 3);
|
||||
assert_eq!(get_chat_msgs(&fiona, msg.chat_id).await?.len(), 2);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&fiona, msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
let msg_from = Contact::get_by_id(&fiona, msg.get_from_id()).await?;
|
||||
assert_eq!(msg_from.get_addr(), "alice@example.org");
|
||||
assert!(sent1_ts_sent < msg.timestamp_sent);
|
||||
@@ -4454,13 +4466,13 @@ async fn test_receive_edit_request_after_removal() -> Result<()> {
|
||||
let bob_msg = bob.recv_msg(&sent1).await;
|
||||
let bob_chat_id = bob_msg.chat_id;
|
||||
assert_eq!(bob_msg.text, "zext me in delra.cat");
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(bob).await?, 1);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(bob).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
delete_msgs(bob, &[bob_msg.id]).await?;
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(bob).await?, 0);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(bob).await?, E2EE_INFO_MSGS);
|
||||
|
||||
bob.recv_msg_trash(&sent2).await;
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(bob).await?, 0);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(bob).await?, E2EE_INFO_MSGS);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4549,28 +4561,34 @@ async fn test_send_delete_request() -> Result<()> {
|
||||
// Alice sends a message, then sends a deletion request
|
||||
let sent1 = alice.send_text(alice_chat.id, "wtf").await;
|
||||
let alice_msg = sent1.load_from_db().await;
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(alice).await?, 2);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(alice).await?, E2EE_INFO_MSGS + 2);
|
||||
|
||||
message::delete_msgs_ex(alice, &[alice_msg.id], true).await?;
|
||||
let sent2 = alice.pop_sent_msg().await;
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(alice).await?, 1);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(alice).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
// Bob receives both messages and has nothing the end
|
||||
let bob_msg = bob.recv_msg(&sent1).await;
|
||||
assert_eq!(bob_msg.text, "wtf");
|
||||
assert_eq!(bob_msg.chat_id.get_msg_cnt(bob).await?, 2);
|
||||
assert_eq!(bob_msg.chat_id.get_msg_cnt(bob).await?, E2EE_INFO_MSGS + 2);
|
||||
|
||||
bob.recv_msg_opt(&sent2).await;
|
||||
assert_eq!(bob_msg.chat_id.get_msg_cnt(bob).await?, 1);
|
||||
assert_eq!(bob_msg.chat_id.get_msg_cnt(bob).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
// Alice has another device, and there is also nothing at the end
|
||||
let alice2 = &tcm.alice().await;
|
||||
alice2.recv_msg(&sent0).await;
|
||||
let alice2_msg = alice2.recv_msg(&sent1).await;
|
||||
assert_eq!(alice2_msg.chat_id.get_msg_cnt(alice2).await?, 2);
|
||||
assert_eq!(
|
||||
alice2_msg.chat_id.get_msg_cnt(alice2).await?,
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
|
||||
alice2.recv_msg_opt(&sent2).await;
|
||||
assert_eq!(alice2_msg.chat_id.get_msg_cnt(alice2).await?, 1);
|
||||
assert_eq!(
|
||||
alice2_msg.chat_id.get_msg_cnt(alice2).await?,
|
||||
E2EE_INFO_MSGS + 1
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::chatlist::Chatlist;
|
||||
use crate::constants::Chattype;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{TestContext, get_chat_msg};
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, get_chat_msg};
|
||||
use crate::tools::{SystemTime, create_outgoing_rfc724_mid};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -571,7 +571,7 @@ async fn test_get_next_msgs() -> Result<()> {
|
||||
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
|
||||
assert!(alice.get_next_msgs().await?.is_empty());
|
||||
assert_eq!(alice.get_next_msgs().await?.len(), E2EE_INFO_MSGS);
|
||||
assert!(bob.get_next_msgs().await?.is_empty());
|
||||
|
||||
let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await;
|
||||
|
||||
+6
-3
@@ -277,7 +277,7 @@ mod tests {
|
||||
use crate::chat::{get_chat_msgs, send_msg};
|
||||
use crate::ephemeral::Timer;
|
||||
use crate::receive_imf::receive_imf_from_inbox;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext};
|
||||
|
||||
#[test]
|
||||
fn test_downloadstate_values() {
|
||||
@@ -459,7 +459,10 @@ mod tests {
|
||||
.await?;
|
||||
let msg = bob.get_last_msg().await;
|
||||
let chat_id = msg.chat_id;
|
||||
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 1);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 1
|
||||
);
|
||||
assert_eq!(msg.download_state(), DownloadState::Available);
|
||||
|
||||
// downloading the status update afterwards expands to nothing and moves the placeholder to trash-chat
|
||||
@@ -472,7 +475,7 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 0);
|
||||
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), E2EE_INFO_MSGS);
|
||||
assert!(
|
||||
Message::load_from_db_optional(&bob, msg.id)
|
||||
.await?
|
||||
|
||||
+3
-3
@@ -209,7 +209,7 @@ impl Client {
|
||||
let tcp_stream = connect_tcp_inner(addr).await?;
|
||||
let account_id = context.get_id();
|
||||
let events = context.events.clone();
|
||||
let logging_stream = LoggingStream::new(tcp_stream, account_id, events);
|
||||
let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
|
||||
let tls_stream = wrap_tls(strict_tls, hostname, alpn(addr.port()), logging_stream).await?;
|
||||
let buffered_stream = BufWriter::new(tls_stream);
|
||||
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
|
||||
@@ -225,7 +225,7 @@ impl Client {
|
||||
let tcp_stream = connect_tcp_inner(addr).await?;
|
||||
let account_id = context.get_id();
|
||||
let events = context.events.clone();
|
||||
let logging_stream = LoggingStream::new(tcp_stream, account_id, events);
|
||||
let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
|
||||
let buffered_stream = BufWriter::new(logging_stream);
|
||||
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
|
||||
let mut client = Client::new(session_stream);
|
||||
@@ -246,7 +246,7 @@ impl Client {
|
||||
|
||||
let account_id = context.get_id();
|
||||
let events = context.events.clone();
|
||||
let tcp_stream = LoggingStream::new(tcp_stream, account_id, events);
|
||||
let tcp_stream = LoggingStream::new(tcp_stream, account_id, events)?;
|
||||
|
||||
// Run STARTTLS command and convert the client back into a stream.
|
||||
let buffered_tcp_stream = BufWriter::new(tcp_stream);
|
||||
|
||||
+18
-17
@@ -10,7 +10,7 @@ use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context as _, Result};
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::events::{Event, EventType, Events};
|
||||
@@ -51,16 +51,26 @@ pub(crate) struct LoggingStream<S: SessionStream> {
|
||||
|
||||
/// Metrics for this stream.
|
||||
metrics: Metrics,
|
||||
|
||||
/// Peer address at the time of creation.
|
||||
///
|
||||
/// Socket may become disconnected later,
|
||||
/// so we save it when `LoggingStream` is created.
|
||||
peer_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl<S: SessionStream> LoggingStream<S> {
|
||||
pub fn new(inner: S, account_id: u32, events: Events) -> Self {
|
||||
Self {
|
||||
pub fn new(inner: S, account_id: u32, events: Events) -> Result<Self> {
|
||||
let peer_addr: SocketAddr = inner
|
||||
.peer_addr()
|
||||
.context("Attempt to create LoggingStream over an unconnected stream")?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
account_id,
|
||||
events,
|
||||
metrics: Metrics::new(),
|
||||
}
|
||||
peer_addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,25 +81,16 @@ impl<S: SessionStream> AsyncRead for LoggingStream<S> {
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let this = self.project();
|
||||
let peer_addr = this.inner.peer_addr();
|
||||
let old_remaining = buf.remaining();
|
||||
|
||||
let res = this.inner.poll_read(cx, buf);
|
||||
|
||||
if let Poll::Ready(Err(ref err)) = res {
|
||||
debug_assert!(
|
||||
peer_addr.is_ok(),
|
||||
"Logging stream should be created over a bound socket"
|
||||
let peer_addr = this.peer_addr;
|
||||
let log_message = format!(
|
||||
"Read error on stream {peer_addr:?} after reading {} and writing {} bytes: {err}.",
|
||||
this.metrics.total_read, this.metrics.total_written
|
||||
);
|
||||
let log_message = match peer_addr {
|
||||
Ok(peer_addr) => format!(
|
||||
"Read error on stream {peer_addr:?} after reading {} and writing {} bytes: {err}.",
|
||||
this.metrics.total_read, this.metrics.total_written
|
||||
),
|
||||
Err(_) => {
|
||||
format!("Read error on a stream that does not have a peer address: {err}.")
|
||||
}
|
||||
};
|
||||
this.events.emit(Event {
|
||||
id: *this.account_id,
|
||||
typ: EventType::Warning(log_message),
|
||||
|
||||
@@ -963,6 +963,7 @@ impl Message {
|
||||
| SystemMessage::SecurejoinMessage
|
||||
| SystemMessage::LocationStreamingEnabled
|
||||
| SystemMessage::LocationOnly
|
||||
| SystemMessage::ChatE2ee
|
||||
| SystemMessage::ChatProtectionEnabled
|
||||
| SystemMessage::ChatProtectionDisabled
|
||||
| SystemMessage::InvalidUnencryptedMail
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::config::Config;
|
||||
use crate::reaction::send_reaction;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager};
|
||||
|
||||
#[test]
|
||||
fn test_guess_msgtype_from_suffix() {
|
||||
@@ -347,7 +347,7 @@ async fn test_markseen_msgs() -> Result<()> {
|
||||
let chats = Chatlist::try_load(&bob, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let msgs = chat::get_chat_msgs(&bob, bob_chat_id).await?;
|
||||
assert_eq!(msgs.len(), 2);
|
||||
assert_eq!(msgs.len(), E2EE_INFO_MSGS + 2);
|
||||
assert_eq!(bob.get_fresh_msgs().await?.len(), 0);
|
||||
|
||||
// that has no effect in contact request
|
||||
@@ -358,7 +358,7 @@ async fn test_markseen_msgs() -> Result<()> {
|
||||
assert_eq!(bob_chat.blocked, Blocked::Request);
|
||||
|
||||
let msgs = chat::get_chat_msgs(&bob, bob_chat_id).await?;
|
||||
assert_eq!(msgs.len(), 2);
|
||||
assert_eq!(msgs.len(), E2EE_INFO_MSGS + 2);
|
||||
bob_chat_id.accept(&bob).await.unwrap();
|
||||
|
||||
// bob sends to alice,
|
||||
@@ -761,19 +761,22 @@ async fn test_delete_msgs_sync() -> Result<()> {
|
||||
|
||||
// Alice sends a messsage and receives it on the other device
|
||||
let sent1 = alice.send_text(alice_chat_id, "foo").await;
|
||||
assert_eq!(alice_chat_id.get_msg_cnt(alice).await?, 1);
|
||||
assert_eq!(alice_chat_id.get_msg_cnt(alice).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
let msg = alice2.recv_msg(&sent1).await;
|
||||
let alice2_chat_id = msg.chat_id;
|
||||
assert_eq!(alice2.get_last_msg_in(alice2_chat_id).await.id, msg.id);
|
||||
assert_eq!(alice2_chat_id.get_msg_cnt(alice2).await?, 1);
|
||||
assert_eq!(
|
||||
alice2_chat_id.get_msg_cnt(alice2).await?,
|
||||
E2EE_INFO_MSGS + 1
|
||||
);
|
||||
|
||||
// Alice deletes the message; this should happen on both devices as well
|
||||
delete_msgs(alice, &[sent1.sender_msg_id]).await?;
|
||||
assert_eq!(alice_chat_id.get_msg_cnt(alice).await?, 0);
|
||||
assert_eq!(alice_chat_id.get_msg_cnt(alice).await?, E2EE_INFO_MSGS);
|
||||
|
||||
test_utils::sync(alice, alice2).await;
|
||||
assert_eq!(alice2_chat_id.get_msg_cnt(alice2).await?, 0);
|
||||
assert_eq!(alice2_chat_id.get_msg_cnt(alice2).await?, E2EE_INFO_MSGS);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+5
-2
@@ -181,10 +181,10 @@ pub enum SystemMessage {
|
||||
/// Chat ephemeral message timer is changed.
|
||||
EphemeralTimerChanged = 10,
|
||||
|
||||
/// "Messages are guaranteed to be end-to-end encrypted from now on."
|
||||
/// "Messages are end-to-end encrypted."
|
||||
ChatProtectionEnabled = 11,
|
||||
|
||||
/// "%1$s sent a message from another device."
|
||||
/// "%1$s sent a message from another device.", deprecated 2025-07
|
||||
ChatProtectionDisabled = 12,
|
||||
|
||||
/// Message can't be sent because of `Invalid unencrypted mail to <>`
|
||||
@@ -213,6 +213,9 @@ pub enum SystemMessage {
|
||||
|
||||
/// This message contains a users iroh node address.
|
||||
IrohNodeAddr = 40,
|
||||
|
||||
/// "Messages are end-to-end encrypted."
|
||||
ChatE2ee = 50,
|
||||
}
|
||||
|
||||
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
|
||||
|
||||
+7
-3
@@ -16,12 +16,14 @@ use crate::sql::Sql;
|
||||
use crate::tools::time;
|
||||
|
||||
pub(crate) mod dns;
|
||||
pub(crate) mod error_capturing_stream;
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod proxy;
|
||||
pub(crate) mod session;
|
||||
pub(crate) mod tls;
|
||||
|
||||
use dns::lookup_host_with_cache;
|
||||
pub(crate) use error_capturing_stream::ErrorCapturingStream;
|
||||
pub use http::{Response as HttpResponse, read_url, read_url_blob};
|
||||
use tls::wrap_tls;
|
||||
|
||||
@@ -105,7 +107,7 @@ pub(crate) async fn load_connection_timestamp(
|
||||
/// to the network, which is important to reduce the latency of interactive protocols such as IMAP.
|
||||
pub(crate) async fn connect_tcp_inner(
|
||||
addr: SocketAddr,
|
||||
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
|
||||
) -> Result<Pin<Box<ErrorCapturingStream<TimeoutStream<TcpStream>>>>> {
|
||||
let tcp_stream = timeout(TIMEOUT, TcpStream::connect(addr))
|
||||
.await
|
||||
.context("connection timeout")?
|
||||
@@ -118,7 +120,9 @@ pub(crate) async fn connect_tcp_inner(
|
||||
timeout_stream.set_write_timeout(Some(TIMEOUT));
|
||||
timeout_stream.set_read_timeout(Some(TIMEOUT));
|
||||
|
||||
Ok(Box::pin(timeout_stream))
|
||||
let error_capturing_stream = ErrorCapturingStream::new(timeout_stream);
|
||||
|
||||
Ok(Box::pin(error_capturing_stream))
|
||||
}
|
||||
|
||||
/// Attempts to establish TLS connection
|
||||
@@ -235,7 +239,7 @@ pub(crate) async fn connect_tcp(
|
||||
host: &str,
|
||||
port: u16,
|
||||
load_cache: bool,
|
||||
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
|
||||
) -> Result<Pin<Box<ErrorCapturingStream<TimeoutStream<TcpStream>>>>> {
|
||||
let connection_futures = lookup_host_with_cache(context, host, port, "", load_cache)
|
||||
.await?
|
||||
.into_iter()
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
use std::io::IoSlice;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use tokio::io::{self, AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::net::SessionStream;
|
||||
|
||||
/// Stream that remembers the first error
|
||||
/// and keeps returning it afterwards.
|
||||
///
|
||||
/// It is needed to avoid accidentally using
|
||||
/// the stream after read timeout.
|
||||
#[derive(Debug)]
|
||||
#[pin_project]
|
||||
pub(crate) struct ErrorCapturingStream<T: AsyncRead + AsyncWrite + std::fmt::Debug> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
|
||||
/// If true, the stream has already returned an error once.
|
||||
///
|
||||
/// All read and write operations return error in this case.
|
||||
is_broken: bool,
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite + std::fmt::Debug> ErrorCapturingStream<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
is_broken: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a reference to the underlying stream.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Gets a pinned mutable reference to the underlying stream.
|
||||
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
|
||||
self.project().inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite + std::fmt::Debug> AsyncRead for ErrorCapturingStream<T> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf,
|
||||
) -> Poll<io::Result<()>> {
|
||||
let this = self.project();
|
||||
if *this.is_broken {
|
||||
return Poll::Ready(Err(io::Error::other("Broken stream")));
|
||||
}
|
||||
let res = this.inner.poll_read(cx, buf);
|
||||
if let Poll::Ready(Err(_)) = res {
|
||||
*this.is_broken = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite + std::fmt::Debug> AsyncWrite for ErrorCapturingStream<T> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let this = self.project();
|
||||
if *this.is_broken {
|
||||
return Poll::Ready(Err(io::Error::other("Broken stream")));
|
||||
}
|
||||
let res = this.inner.poll_write(cx, buf);
|
||||
if let Poll::Ready(Err(_)) = res {
|
||||
*this.is_broken = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let this = self.project();
|
||||
if *this.is_broken {
|
||||
return Poll::Ready(Err(io::Error::other("Broken stream")));
|
||||
}
|
||||
let res = this.inner.poll_flush(cx);
|
||||
if let Poll::Ready(Err(_)) = res {
|
||||
*this.is_broken = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let this = self.project();
|
||||
if *this.is_broken {
|
||||
return Poll::Ready(Err(io::Error::other("Broken stream")));
|
||||
}
|
||||
let res = this.inner.poll_shutdown(cx);
|
||||
if let Poll::Ready(Err(_)) = res {
|
||||
*this.is_broken = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let this = self.project();
|
||||
if *this.is_broken {
|
||||
return Poll::Ready(Err(io::Error::other("Broken stream")));
|
||||
}
|
||||
let res = this.inner.poll_write_vectored(cx, bufs);
|
||||
if let Poll::Ready(Err(_)) = res {
|
||||
*this.is_broken = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.inner.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SessionStream> SessionStream for ErrorCapturingStream<T> {
|
||||
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
|
||||
self.inner.set_read_timeout(timeout)
|
||||
}
|
||||
|
||||
fn peer_addr(&self) -> anyhow::Result<SocketAddr> {
|
||||
self.inner.peer_addr()
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -21,9 +21,9 @@ use url::Url;
|
||||
use crate::config::Config;
|
||||
use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT;
|
||||
use crate::context::Context;
|
||||
use crate::net::connect_tcp;
|
||||
use crate::net::session::SessionStream;
|
||||
use crate::net::tls::wrap_rustls;
|
||||
use crate::net::{ErrorCapturingStream, connect_tcp};
|
||||
use crate::sql::Sql;
|
||||
|
||||
/// Default SOCKS5 port according to [RFC 1928](https://tools.ietf.org/html/rfc1928).
|
||||
@@ -118,7 +118,7 @@ impl Socks5Config {
|
||||
target_host: &str,
|
||||
target_port: u16,
|
||||
load_dns_cache: bool,
|
||||
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
|
||||
) -> Result<Socks5Stream<Pin<Box<ErrorCapturingStream<TimeoutStream<TcpStream>>>>>> {
|
||||
let tcp_stream = connect_tcp(context, &self.host, self.port, load_dns_cache)
|
||||
.await
|
||||
.context("Failed to connect to SOCKS5 proxy")?;
|
||||
|
||||
+5
-3
@@ -7,6 +7,8 @@ use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, BufStream, BufWriter};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_io_timeout::TimeoutStream;
|
||||
|
||||
use crate::net::ErrorCapturingStream;
|
||||
|
||||
pub(crate) trait SessionStream:
|
||||
AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug
|
||||
{
|
||||
@@ -61,13 +63,13 @@ impl<T: SessionStream> SessionStream for BufWriter<T> {
|
||||
self.get_ref().peer_addr()
|
||||
}
|
||||
}
|
||||
impl SessionStream for Pin<Box<TimeoutStream<TcpStream>>> {
|
||||
impl SessionStream for Pin<Box<ErrorCapturingStream<TimeoutStream<TcpStream>>>> {
|
||||
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
|
||||
self.as_mut().set_read_timeout_pinned(timeout);
|
||||
self.as_mut().get_pin_mut().set_read_timeout_pinned(timeout);
|
||||
}
|
||||
|
||||
fn peer_addr(&self) -> Result<SocketAddr> {
|
||||
Ok(self.get_ref().peer_addr()?)
|
||||
Ok(self.get_ref().get_ref().peer_addr()?)
|
||||
}
|
||||
}
|
||||
impl<T: SessionStream> SessionStream for Socks5Stream<T> {
|
||||
|
||||
+25
-6
@@ -407,6 +407,7 @@ mod tests {
|
||||
use crate::message::{MessageState, delete_msgs};
|
||||
use crate::receive_imf::{receive_imf, receive_imf_from_inbox};
|
||||
use crate::sql::housekeeping;
|
||||
use crate::test_utils::E2EE_INFO_MSGS;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::test_utils::TestContextManager;
|
||||
use crate::tools::SystemTime;
|
||||
@@ -653,13 +654,25 @@ Here's my footer -- bob@example.net"
|
||||
let chat_alice = alice.create_chat(&bob).await;
|
||||
let alice_msg = alice.send_text(chat_alice.id, "Hi!").await;
|
||||
let bob_msg = bob.recv_msg(&alice_msg).await;
|
||||
assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 1);
|
||||
assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 1);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&alice, chat_alice.id).await?.len(),
|
||||
E2EE_INFO_MSGS + 1
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, bob_msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 1
|
||||
);
|
||||
|
||||
let alice_msg2 = alice.send_text(chat_alice.id, "Hi again!").await;
|
||||
bob.recv_msg(&alice_msg2).await;
|
||||
assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 2);
|
||||
assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 2);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&alice, chat_alice.id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, bob_msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
|
||||
bob_msg.chat_id.accept(&bob).await?;
|
||||
|
||||
@@ -667,12 +680,18 @@ Here's my footer -- bob@example.net"
|
||||
send_reaction(&bob, bob_msg.id, "👍").await.unwrap();
|
||||
expect_reactions_changed_event(&bob, bob_msg.chat_id, bob_msg.id, ContactId::SELF).await?;
|
||||
expect_no_unwanted_events(&bob).await;
|
||||
assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 2);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&bob, bob_msg.chat_id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
|
||||
let bob_reaction_msg = bob.pop_sent_msg().await;
|
||||
let alice_reaction_msg = alice.recv_msg_hidden(&bob_reaction_msg).await;
|
||||
assert_eq!(alice_reaction_msg.state, MessageState::InFresh);
|
||||
assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 2);
|
||||
assert_eq!(
|
||||
get_chat_msgs(&alice, chat_alice.id).await?.len(),
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
|
||||
let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?;
|
||||
assert_eq!(reactions.to_string(), "👍1");
|
||||
|
||||
@@ -15,8 +15,9 @@ use crate::download::MIN_DOWNLOAD_LIMIT;
|
||||
use crate::imap::prefetch_should_download;
|
||||
use crate::imex::{ImexMode, imex};
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
use crate::test_utils::mark_as_verified;
|
||||
use crate::test_utils::{TestContext, TestContextManager, get_chat_msg};
|
||||
use crate::test_utils::{
|
||||
E2EE_INFO_MSGS, TestContext, TestContextManager, get_chat_msg, mark_as_verified,
|
||||
};
|
||||
use crate::tools::{SystemTime, time};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -133,7 +134,7 @@ async fn test_adhoc_group_outgoing_show_accepted_contact_unaccepted() -> Result<
|
||||
let chats = Chatlist::try_load(bob, 0, None, None).await?;
|
||||
assert_eq!(chats.len(), 1);
|
||||
let chat_id = chats.get_chat_id(0)?;
|
||||
assert_eq!(chat_id.get_msg_cnt(bob).await?, 1);
|
||||
assert_eq!(chat_id.get_msg_cnt(bob).await?, E2EE_INFO_MSGS + 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4410,7 +4411,7 @@ async fn test_create_group_with_big_msg() -> Result<()> {
|
||||
|
||||
// The big message must go away from the 1:1 chat.
|
||||
let msgs = chat::get_chat_msgs(&alice, ab_chat_id).await?;
|
||||
assert!(msgs.is_empty());
|
||||
assert_eq!(msgs.len(), E2EE_INFO_MSGS);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::chatlist::Chatlist;
|
||||
use crate::constants::Chattype;
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::stock_str::{self, chat_protection_enabled};
|
||||
use crate::stock_str::{self, messages_e2e_encrypted};
|
||||
use crate::test_utils::{
|
||||
TestContext, TestContextManager, TimeShiftFalsePositiveNote, get_chat_msg,
|
||||
};
|
||||
@@ -246,7 +246,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
let chat = alice.get_chat(&bob).await;
|
||||
let msg = get_chat_msg(&alice, chat.get_id(), 0, 1).await;
|
||||
assert!(msg.is_info());
|
||||
let expected_text = chat_protection_enabled(&alice).await;
|
||||
let expected_text = messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg.get_text(), expected_text);
|
||||
if case == SetupContactCase::CheckProtectionTimestamp {
|
||||
assert_eq!(msg.timestamp_sort, vc_request_with_auth_ts_sent + 1);
|
||||
@@ -296,7 +296,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
|
||||
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob).await);
|
||||
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
|
||||
assert!(msg.is_info());
|
||||
assert_eq!(msg.get_text(), chat_protection_enabled(&bob).await);
|
||||
assert_eq!(msg.get_text(), messages_e2e_encrypted(&bob).await);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -540,7 +540,7 @@ async fn test_secure_join() -> Result<()> {
|
||||
// - You added member bob@example.net
|
||||
let msg = get_chat_msg(&alice, alice_chatid, 0, 2).await;
|
||||
assert!(msg.is_info());
|
||||
let expected_text = chat_protection_enabled(&alice).await;
|
||||
let expected_text = messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg.get_text(), expected_text);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+5
-7
@@ -65,9 +65,6 @@ pub enum StockMessage {
|
||||
#[strum(props(fallback = "GIF"))]
|
||||
Gif = 23,
|
||||
|
||||
#[strum(props(fallback = "Encrypted message"))]
|
||||
EncryptedMsg = 24,
|
||||
|
||||
#[strum(props(fallback = "End-to-end encryption available"))]
|
||||
E2eAvailable = 25,
|
||||
|
||||
@@ -380,9 +377,10 @@ pub enum StockMessage {
|
||||
#[strum(props(fallback = "I left the group."))]
|
||||
MsgILeftGroup = 166,
|
||||
|
||||
#[strum(props(fallback = "Messages are guaranteed to be end-to-end encrypted from now on."))]
|
||||
#[strum(props(fallback = "Messages are end-to-end encrypted."))]
|
||||
ChatProtectionEnabled = 170,
|
||||
|
||||
// deprecated 2025-07
|
||||
#[strum(props(fallback = "%1$s sent a message from another device."))]
|
||||
ChatProtectionDisabled = 171,
|
||||
|
||||
@@ -1031,8 +1029,8 @@ pub(crate) async fn error_no_network(context: &Context) -> String {
|
||||
translated(context, StockMessage::ErrorNoNetwork).await
|
||||
}
|
||||
|
||||
/// Stock string: `Messages are guaranteed to be end-to-end encrypted from now on.`
|
||||
pub(crate) async fn chat_protection_enabled(context: &Context) -> String {
|
||||
/// Stock string: `Messages are end-to-end encrypted.`
|
||||
pub(crate) async fn messages_e2e_encrypted(context: &Context) -> String {
|
||||
translated(context, StockMessage::ChatProtectionEnabled).await
|
||||
}
|
||||
|
||||
@@ -1303,7 +1301,7 @@ impl Context {
|
||||
"[Error] No contact_id given".to_string()
|
||||
}
|
||||
}
|
||||
ProtectionStatus::Protected => chat_protection_enabled(self).await,
|
||||
ProtectionStatus::Protected => messages_e2e_encrypted(self).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ use crate::securejoin::{get_securejoin_qr, join_securejoin};
|
||||
use crate::stock_str::StockStrings;
|
||||
use crate::tools::time;
|
||||
|
||||
/// The number of info messages added to new e2ee chats.
|
||||
/// Currently this is "End-to-end encryption available", string `E2eAvailable`.
|
||||
pub const E2EE_INFO_MSGS: usize = 1;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const AVATAR_900x900_BYTES: &[u8] = include_bytes!("../test-data/image/avatar900x900.png");
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ use crate::mimeparser::SystemMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::securejoin::{get_securejoin_qr, join_securejoin};
|
||||
use crate::stock_str;
|
||||
use crate::test_utils::{TestContext, TestContextManager, get_chat_msg, mark_as_verified};
|
||||
use crate::test_utils::{
|
||||
E2EE_INFO_MSGS, TestContext, TestContextManager, get_chat_msg, mark_as_verified,
|
||||
};
|
||||
use crate::tools::SystemTime;
|
||||
use crate::{e2ee, message};
|
||||
|
||||
@@ -132,7 +134,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
assert!(chat.is_protected());
|
||||
|
||||
let msg = get_chat_msg(&alice, chat.id, 0, 1).await;
|
||||
let expected_text = stock_str::chat_protection_enabled(&alice).await;
|
||||
let expected_text = stock_str::messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg.text, expected_text);
|
||||
}
|
||||
|
||||
@@ -142,7 +144,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
assert!(chat.is_protected());
|
||||
|
||||
let msg0 = get_chat_msg(&fiona, chat.id, 0, 1).await;
|
||||
let expected_text = stock_str::chat_protection_enabled(&fiona).await;
|
||||
let expected_text = stock_str::messages_e2e_encrypted(&fiona).await;
|
||||
assert_eq!(msg0.text, expected_text);
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
|
||||
let chat = alice.get_chat(&fiona_new).await;
|
||||
assert!(!chat.is_protected());
|
||||
|
||||
let msg = get_chat_msg(&alice, chat.id, 0, 1).await;
|
||||
let msg = get_chat_msg(&alice, chat.id, 1, E2EE_INFO_MSGS + 1).await;
|
||||
assert_eq!(msg.text, "I have a new device");
|
||||
|
||||
// After recreating the chat, it should still be unprotected
|
||||
@@ -268,7 +270,7 @@ async fn test_degrade_verified_oneonone_chat() -> Result<()> {
|
||||
.await?;
|
||||
|
||||
let msg0 = get_chat_msg(&alice, alice_chat.id, 0, 1).await;
|
||||
let enabled = stock_str::chat_protection_enabled(&alice).await;
|
||||
let enabled = stock_str::messages_e2e_encrypted(&alice).await;
|
||||
assert_eq!(msg0.text, enabled);
|
||||
assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatProtectionEnabled);
|
||||
|
||||
|
||||
+16
-13
@@ -13,7 +13,7 @@ use crate::config::Config;
|
||||
use crate::download::DownloadState;
|
||||
use crate::ephemeral;
|
||||
use crate::receive_imf::{receive_imf, receive_imf_from_inbox};
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager};
|
||||
use crate::tools::{self, SystemTime};
|
||||
use crate::{message, sql};
|
||||
|
||||
@@ -250,7 +250,7 @@ async fn test_resend_webxdc_instance_and_info() -> Result<()> {
|
||||
);
|
||||
let bob_grp = bob_instance.chat_id;
|
||||
assert_eq!(bob.get_last_msg_in(bob_grp).await.id, bob_instance.id);
|
||||
assert_eq!(bob_grp.get_msg_cnt(&bob).await?, 1);
|
||||
assert_eq!(bob_grp.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -869,14 +869,14 @@ async fn test_send_big_webxdc_status_update() -> Result<()> {
|
||||
let sent2 = &alice.pop_sent_msg().await;
|
||||
let alice_update = sent2.load_from_db().await;
|
||||
assert_eq!(alice_update.text, BODY_DESCR.to_string());
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 1);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
// Bob receives the instance.
|
||||
let bob_instance = bob.recv_msg(sent1).await;
|
||||
let bob_chat_id = bob_instance.chat_id;
|
||||
assert_eq!(bob_instance.rfc724_mid, alice_instance.rfc724_mid);
|
||||
assert_eq!(bob_instance.viewtype, Viewtype::Webxdc);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 1);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
// Bob receives the status updates.
|
||||
bob.recv_msg_trash(sent2).await;
|
||||
@@ -896,7 +896,7 @@ async fn test_send_big_webxdc_status_update() -> Result<()> {
|
||||
r#"[{"payload":{"foo":"bar2"},"serial":2,"max_serial":3},
|
||||
{"payload":{"foo":"bar3"},"serial":3,"max_serial":3}]"#
|
||||
);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 1);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1485,7 +1485,7 @@ async fn test_webxdc_info_msg() -> Result<()> {
|
||||
let alice_chat = alice.create_chat(&bob).await;
|
||||
let alice_instance = send_webxdc_instance(&alice, alice_chat.id).await?;
|
||||
let sent1 = &alice.pop_sent_msg().await;
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 1);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, E2EE_INFO_MSGS + 1);
|
||||
|
||||
alice
|
||||
.send_webxdc_status_update(
|
||||
@@ -1495,7 +1495,7 @@ async fn test_webxdc_info_msg() -> Result<()> {
|
||||
.await?;
|
||||
alice.flush_status_updates().await?;
|
||||
let sent2 = &alice.pop_sent_msg().await;
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 2);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, E2EE_INFO_MSGS + 2);
|
||||
let info_msg = alice.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert_eq!(info_msg.get_info_type(), SystemMessage::WebxdcInfoMessage);
|
||||
@@ -1517,7 +1517,7 @@ async fn test_webxdc_info_msg() -> Result<()> {
|
||||
let bob_instance = bob.recv_msg(sent1).await;
|
||||
let bob_chat_id = bob_instance.chat_id;
|
||||
bob.recv_msg_trash(sent2).await;
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 2);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 2);
|
||||
let info_msg = bob.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert_eq!(info_msg.get_info_type(), SystemMessage::WebxdcInfoMessage);
|
||||
@@ -1536,7 +1536,10 @@ async fn test_webxdc_info_msg() -> Result<()> {
|
||||
let alice2_instance = alice2.recv_msg(sent1).await;
|
||||
let alice2_chat_id = alice2_instance.chat_id;
|
||||
alice2.recv_msg_trash(sent2).await;
|
||||
assert_eq!(alice2_chat_id.get_msg_cnt(&alice2).await?, 2);
|
||||
assert_eq!(
|
||||
alice2_chat_id.get_msg_cnt(&alice2).await?,
|
||||
E2EE_INFO_MSGS + 2
|
||||
);
|
||||
let info_msg = alice2.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert_eq!(info_msg.get_info_type(), SystemMessage::WebxdcInfoMessage);
|
||||
@@ -1572,13 +1575,13 @@ async fn test_webxdc_info_msg_cleanup_series() -> Result<()> {
|
||||
.await?;
|
||||
alice.flush_status_updates().await?;
|
||||
let sent2 = &alice.pop_sent_msg().await;
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 2);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, E2EE_INFO_MSGS + 2);
|
||||
alice
|
||||
.send_webxdc_status_update(alice_instance.id, r#"{"info":"i2", "payload":2}"#)
|
||||
.await?;
|
||||
alice.flush_status_updates().await?;
|
||||
let sent3 = &alice.pop_sent_msg().await;
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 2);
|
||||
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, E2EE_INFO_MSGS + 2);
|
||||
let info_msg = alice.get_last_msg().await;
|
||||
assert_eq!(info_msg.get_text(), "i2");
|
||||
|
||||
@@ -1586,9 +1589,9 @@ async fn test_webxdc_info_msg_cleanup_series() -> Result<()> {
|
||||
let bob_instance = bob.recv_msg(sent1).await;
|
||||
let bob_chat_id = bob_instance.chat_id;
|
||||
bob.recv_msg_trash(sent2).await;
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 2);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 2);
|
||||
bob.recv_msg_trash(sent3).await;
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 2);
|
||||
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, E2EE_INFO_MSGS + 2);
|
||||
let info_msg = bob.get_last_msg().await;
|
||||
assert_eq!(info_msg.get_text(), "i2");
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
Group#Chat#10: Group chat [3 member(s)]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10🔒: (Contact#Contact#10): Hi! I created a group. [FRESH]
|
||||
Msg#11🔒: Me (Contact#Contact#Self): You left. [INFO] √
|
||||
Msg#12🔒: (Contact#Contact#10): Member charlie@example.net added by alice@example.org. [FRESH][INFO]
|
||||
Msg#13🔒: (Contact#Contact#10): What a silence! [FRESH]
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
|
||||
Msg#11🔒: (Contact#Contact#10): Hi! I created a group. [FRESH]
|
||||
Msg#12🔒: Me (Contact#Contact#Self): You left. [INFO] √
|
||||
Msg#13🔒: (Contact#Contact#10): Member charlie@example.net added by alice@example.org. [FRESH][INFO]
|
||||
Msg#14🔒: (Contact#Contact#10): What a silence! [FRESH]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Single#Chat#10: bob@example.net [KEY bob@example.net] 🛡️
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO 🛡️]
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#11🔒: Me (Contact#Contact#Self): Test – This is encrypted, signed, and has an Autocrypt Header without prefer-encrypt=mutual. √
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Single#Chat#10: bob@example.net [KEY bob@example.net] 🛡️
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO 🛡️]
|
||||
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#11🔒: (Contact#Contact#10): Heyho from DC [FRESH]
|
||||
Msg#13🔒: Me (Contact#Contact#Self): Sending with DC again √
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@@ -4,6 +4,6 @@ Msg#11: info (Contact#Contact#Info): alice@example.org invited you to join this
|
||||
|
||||
Waiting for the device of alice@example.org to reply… [NOTICED][INFO]
|
||||
Msg#13: info (Contact#Contact#Info): alice@example.org replied, waiting for being added to the group… [NOTICED][INFO]
|
||||
Msg#17: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO 🛡️]
|
||||
Msg#17: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO 🛡️]
|
||||
Msg#18🔒: (Contact#Contact#10): Member Me added by alice@example.org. [FRESH][INFO]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user