mirror of
https://github.com/chatmail/core.git
synced 2026-10-18 10:53:07 +03:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3617b95a9d | ||
|
|
30a196c483 | ||
|
|
35555ca753 | ||
|
|
0bb3d88bc6 | ||
|
|
e74ffb6e93 | ||
|
|
bbcfa5e40e |
@@ -638,22 +638,6 @@ int dc_get_connectivity (dc_context_t* context);
|
||||
char* dc_get_connectivity_html (dc_context_t* context);
|
||||
|
||||
|
||||
#define DC_PUSH_NOT_CONNECTED 0
|
||||
#define DC_PUSH_CONNECTED 2
|
||||
|
||||
/**
|
||||
* Get the current push notification state.
|
||||
* One of:
|
||||
* - DC_PUSH_NOT_CONNECTED
|
||||
* - DC_PUSH_CONNECTED
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @return Push notification state.
|
||||
*/
|
||||
int dc_get_push_state (dc_context_t* context);
|
||||
|
||||
|
||||
// connect
|
||||
|
||||
/**
|
||||
|
||||
@@ -397,16 +397,6 @@ pub unsafe extern "C" fn dc_get_connectivity_html(
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn dc_get_push_state(context: *const dc_context_t) -> libc::c_int {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_get_push_state()");
|
||||
return 0;
|
||||
}
|
||||
let ctx = unsafe { &*context };
|
||||
ctx.push_state() as libc::c_int
|
||||
}
|
||||
|
||||
fn spawn_configure(ctx: Context) {
|
||||
spawn(async move {
|
||||
ctx.configure()
|
||||
|
||||
@@ -53,7 +53,6 @@ use types::contact::{ContactObject, VcardContact};
|
||||
use types::events::Event;
|
||||
use types::http::HttpResponse;
|
||||
use types::message::{MessageData, MessageObject, MessageReadReceipt};
|
||||
use types::notify_state::JsonrpcNotifyState;
|
||||
use types::reactions::JsonrpcReactions;
|
||||
use types::webxdc::WebxdcMessageInfo;
|
||||
|
||||
@@ -328,12 +327,6 @@ impl CommandApi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current push notification state.
|
||||
async fn get_push_state(&self, account_id: u32) -> Result<JsonrpcNotifyState> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
Ok(ctx.push_state().into())
|
||||
}
|
||||
|
||||
/// Get the combined filesize of an account in bytes
|
||||
async fn get_account_file_size(&self, account_id: u32) -> Result<u64> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
@@ -8,8 +8,6 @@ pub mod http;
|
||||
pub mod location;
|
||||
pub mod login_param;
|
||||
pub mod message;
|
||||
pub mod notify_state;
|
||||
|
||||
pub mod qr;
|
||||
pub mod reactions;
|
||||
pub mod webxdc;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
use deltachat::push::NotifyState;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "NotifyState")]
|
||||
pub enum JsonrpcNotifyState {
|
||||
/// Not subscribed to push notifications.
|
||||
NotConnected,
|
||||
|
||||
/// Subscribed to push notifications for new messages.
|
||||
Connected,
|
||||
}
|
||||
|
||||
impl From<NotifyState> for JsonrpcNotifyState {
|
||||
fn from(state: NotifyState) -> Self {
|
||||
match state {
|
||||
NotifyState::NotConnected => Self::NotConnected,
|
||||
NotifyState::Connected => Self::Connected,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ const constants = data
|
||||
key.startsWith("DC_CERTCK_") ||
|
||||
key.startsWith("DC_SOCKET_") ||
|
||||
key.startsWith("DC_LP_AUTH_") ||
|
||||
key.startsWith("DC_PUSH_") ||
|
||||
key.startsWith("DC_TEXT1_") ||
|
||||
key.startsWith("DC_CHAT_TYPE")
|
||||
);
|
||||
|
||||
@@ -246,13 +246,6 @@ class ProviderStatus(IntEnum):
|
||||
BROKEN = 3
|
||||
|
||||
|
||||
class PushNotifyState(IntEnum):
|
||||
"""Push notifications state."""
|
||||
|
||||
NOT_CONNECTED = 0
|
||||
CONNECTED = 2
|
||||
|
||||
|
||||
class ShowEmails(IntEnum):
|
||||
"""Show emails mode."""
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import platform
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import pytest
|
||||
@@ -282,8 +283,15 @@ def alice_and_remote_bob(tmp_path, acfactory, get_core_python_env):
|
||||
channel = gw.remote_exec(remote_bob_loop)
|
||||
cm = os.environ.get("CHATMAIL_DOMAIN")
|
||||
|
||||
# old cores need "ic=3" to accept
|
||||
# the self-signed cert of an underscore domain
|
||||
addr, password = acfactory.get_credentials()
|
||||
dclogin_qr = f"dclogin://{urllib.parse.quote(addr, safe='@')}?p={urllib.parse.quote(password)}&v=1"
|
||||
if cm and cm.startswith("_"):
|
||||
dclogin_qr += "&ic=3"
|
||||
|
||||
# trigger getting an online account on bob's side
|
||||
channel.send((accounts_dir, str(rpc_server_path), cm))
|
||||
channel.send((accounts_dir, str(rpc_server_path), dclogin_qr))
|
||||
|
||||
# meanwhile get a local alice account
|
||||
alice = acfactory.get_online_account()
|
||||
@@ -315,10 +323,8 @@ def remote_bob_loop(channel):
|
||||
import os
|
||||
|
||||
from deltachat_rpc_client import DeltaChat, Rpc
|
||||
from deltachat_rpc_client.pytestplugin import ACFactory
|
||||
|
||||
accounts_dir, rpc_server_path, chatmail_domain = channel.receive()
|
||||
os.environ["CHATMAIL_DOMAIN"] = chatmail_domain
|
||||
accounts_dir, rpc_server_path, dclogin_qr = channel.receive()
|
||||
|
||||
# older core versions don't support specifying rpc_server_path
|
||||
# so we can't just pass `rpc_server_path` argument to Rpc constructor
|
||||
@@ -329,8 +335,13 @@ def remote_bob_loop(channel):
|
||||
with rpc:
|
||||
dc = DeltaChat(rpc)
|
||||
channel.send(dc.rpc.get_system_info()["deltachat_core_version"])
|
||||
acfactory = ACFactory(dc)
|
||||
bob = acfactory.get_online_account()
|
||||
|
||||
# ACFactory would configure from a "dcaccount" QR,
|
||||
# which old cores cannot use on underscore domains
|
||||
bob = dc.add_account()
|
||||
bob.set_config_from_qr(dclogin_qr)
|
||||
bob.bring_online()
|
||||
|
||||
alice_vcard = channel.receive()
|
||||
[alice_contact] = bob.import_vcard(alice_vcard)
|
||||
ns = {"bob": bob, "bob_contact_alice": alice_contact}
|
||||
|
||||
@@ -37,7 +37,11 @@ class DirectImap:
|
||||
host = user.rsplit("@")[-1]
|
||||
pw = self.account.get_config("mail_pw")
|
||||
|
||||
self.conn = MailBox(host, port, ssl_context=ssl.create_default_context())
|
||||
ssl_context = ssl.create_default_context()
|
||||
if host.startswith("_"):
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
self.conn = MailBox(host, port, ssl_context=ssl_context)
|
||||
self.conn.login(user, pw)
|
||||
|
||||
self.select_folder("INBOX")
|
||||
|
||||
@@ -110,7 +110,7 @@ def test_delivery_status_failed(acfactory: ACFactory) -> None:
|
||||
assert failing_message.get_snapshot().state == const.MessageState.OUT_FAILED
|
||||
|
||||
|
||||
def test_download_on_demand(acfactory: ACFactory) -> None:
|
||||
def test_download_on_demand(acfactory: ACFactory, data) -> None:
|
||||
"""
|
||||
Test if download on demand emits chatlist update events.
|
||||
This is only needed for last message in chat, but finding that out is too expensive, so it's always emitted
|
||||
@@ -128,7 +128,7 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
|
||||
msg.get_snapshot().chat.accept()
|
||||
bob.get_chat_by_id(chat_id).send_message(
|
||||
"Hello World, this message is bigger than 5 bytes",
|
||||
file="../test-data/image/screenshot.jpg",
|
||||
file=data.get_path("image/screenshot.jpg"),
|
||||
)
|
||||
|
||||
message = alice.wait_for_incoming_msg()
|
||||
|
||||
@@ -45,7 +45,7 @@ def test_qr_setup_contact(acfactory, alice_and_remote_bob, version) -> None:
|
||||
|
||||
def test_send_and_receive_message(alice_and_remote_bob) -> None:
|
||||
"""Test other-core Bob profile can send a message to Alice on current core."""
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0")
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.24.0")
|
||||
|
||||
remote_eval("bob_contact_alice.create_chat().send_text('hello')")
|
||||
|
||||
@@ -55,7 +55,7 @@ def test_send_and_receive_message(alice_and_remote_bob) -> None:
|
||||
|
||||
def test_second_device(acfactory, alice_and_remote_bob) -> None:
|
||||
"""Test setting up current version as a second device for old version."""
|
||||
_alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0")
|
||||
_alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.24.0")
|
||||
|
||||
remote_eval("locals().setdefault('future', bob._rpc.provide_backup.future(bob.id))")
|
||||
qr = remote_eval("bob._rpc.get_backup_qr(bob.id)")
|
||||
|
||||
@@ -17,6 +17,12 @@ import pytest
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _xfail_underscore_domain():
|
||||
if os.environ.get("CHATMAIL_DOMAIN", "").startswith("_"):
|
||||
pytest.xfail("iroh does not support underscore domains")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def path_to_webxdc(request):
|
||||
p = request.path.parent.parent.parent.joinpath("test-data/webxdc/chess.xdc")
|
||||
|
||||
@@ -76,7 +76,7 @@ def test_change_address(acfactory) -> None:
|
||||
assert sender_addr2 == new_alice_addr
|
||||
|
||||
|
||||
def test_download_on_demand(acfactory) -> None:
|
||||
def test_download_on_demand(acfactory, data) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice.set_config("download_limit", "1")
|
||||
|
||||
@@ -87,7 +87,7 @@ def test_download_on_demand(acfactory) -> None:
|
||||
|
||||
alice.create_chat(bob)
|
||||
chat_bob_alice = bob.create_chat(alice)
|
||||
chat_bob_alice.send_message(file="../test-data/image/screenshot.jpg")
|
||||
chat_bob_alice.send_message(file=data.get_path("image/screenshot.jpg"))
|
||||
msg = alice.wait_for_incoming_msg()
|
||||
snapshot = msg.get_snapshot()
|
||||
assert snapshot.download_state == DownloadState.AVAILABLE
|
||||
|
||||
@@ -105,8 +105,11 @@ def test_lowercase_address(acfactory) -> None:
|
||||
|
||||
def test_configure_ip(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
domain = addr.rsplit("@")[-1]
|
||||
if domain.startswith("_"):
|
||||
pytest.skip("Underscore domains accept invalid certificates")
|
||||
account = acfactory.get_unconfigured_account()
|
||||
ip_address = socket.gethostbyname(addr.rsplit("@")[-1])
|
||||
ip_address = socket.gethostbyname(domain)
|
||||
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_or_update_transport(
|
||||
@@ -1407,7 +1410,7 @@ def test_synchronize_member_list_on_group_rejoin(acfactory, log):
|
||||
assert msg.get_snapshot().chat.num_contacts() == 2
|
||||
|
||||
|
||||
def test_large_message(acfactory) -> None:
|
||||
def test_large_message(acfactory, data) -> None:
|
||||
"""
|
||||
Test sending large message without download limit set,
|
||||
so it is sent with pre-message but downloaded without user interaction.
|
||||
@@ -1417,7 +1420,7 @@ def test_large_message(acfactory) -> None:
|
||||
alice_chat_bob = alice.create_chat(bob)
|
||||
alice_chat_bob.send_message(
|
||||
"Hello World, this message is bigger than 5 bytes",
|
||||
file="../test-data/image/screenshot.jpg",
|
||||
file=data.get_path("image/screenshot.jpg"),
|
||||
)
|
||||
|
||||
msg = bob.wait_for_incoming_msg()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
def test_webxdc(acfactory) -> None:
|
||||
def test_webxdc(acfactory, data) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||
alice_chat_bob.send_message(text="Let's play chess!", file=data.get_path("webxdc/chess.xdc"))
|
||||
|
||||
event = bob.wait_for_incoming_msg_event()
|
||||
bob_chat_alice = bob.get_chat_by_id(event.chat_id)
|
||||
@@ -43,12 +43,12 @@ def test_webxdc(acfactory) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_webxdc_insert_lots_of_updates(acfactory) -> None:
|
||||
def test_webxdc_insert_lots_of_updates(acfactory, data) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
message = alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||
message = alice_chat_bob.send_message(text="Let's play chess!", file=data.get_path("webxdc/chess.xdc"))
|
||||
|
||||
for i in range(2000):
|
||||
message.send_webxdc_status_update({"payload": str(i)}, "description")
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsString;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, OnceLock, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -296,9 +295,6 @@ pub struct InnerContext {
|
||||
/// Push subscriber to store device token.
|
||||
pub(crate) push_subscriber: PushSubscriber,
|
||||
|
||||
/// True if account has subscribed to push notifications via IMAP.
|
||||
pub(crate) push_subscribed: AtomicBool,
|
||||
|
||||
/// TLS session resumption cache.
|
||||
pub(crate) tls_session_store: TlsSessionStore,
|
||||
|
||||
@@ -497,7 +493,6 @@ impl Context {
|
||||
migration_error: parking_lot::RwLock::new(None),
|
||||
debug_logging: std::sync::RwLock::new(None),
|
||||
push_subscriber,
|
||||
push_subscribed: AtomicBool::new(false),
|
||||
tls_session_store: TlsSessionStore::new(),
|
||||
spki_hash_store: SpkiHashStore::new(),
|
||||
iroh: Arc::new(RwLock::new(None)),
|
||||
|
||||
+66
-64
@@ -10,7 +10,6 @@ use std::{
|
||||
iter::Peekable,
|
||||
mem::take,
|
||||
str::FromStr,
|
||||
sync::atomic::Ordering,
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
@@ -126,6 +125,11 @@ pub(crate) struct ServerMetadata {
|
||||
/// Maximum number of recipients for SMTP `RCPT TO:`.
|
||||
pub max_smtp_rcpt_to: Option<u32>,
|
||||
|
||||
/// True if we think the relay supports push notifications.
|
||||
/// This gates wether we attempt to write an encrypted device token
|
||||
/// to per-transport IMAP metadata key `/private/devicetoken`.
|
||||
pub supports_push: bool,
|
||||
|
||||
/// ICE servers for WebRTC calls.
|
||||
pub ice_servers: Vec<UnresolvedIceServer>,
|
||||
|
||||
@@ -1411,6 +1415,7 @@ impl Session {
|
||||
admin,
|
||||
iroh_relay,
|
||||
max_smtp_rcpt_to,
|
||||
supports_push: max_smtp_rcpt_to.is_some() || self.capabilities.has_xdeltapush,
|
||||
ice_servers,
|
||||
ice_servers_expiration_timestamp,
|
||||
},
|
||||
@@ -1420,44 +1425,43 @@ impl Session {
|
||||
|
||||
/// Stores device token into /private/devicetoken IMAP METADATA of the Inbox.
|
||||
pub(crate) async fn register_token(&mut self, context: &Context) -> Result<()> {
|
||||
if context.push_subscribed.load(Ordering::Relaxed) {
|
||||
// `update_metadata` ran before and computed `supports_push`.
|
||||
if self.push_token_registered
|
||||
|| !context
|
||||
.metadata
|
||||
.read()
|
||||
.await
|
||||
.get(&self.transport_id())
|
||||
.is_some_and(|metadata| metadata.supports_push)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let transport_id = self.transport_id();
|
||||
|
||||
let Some(device_token) = context.push_subscriber.device_token() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if self.can_metadata() && self.can_push() {
|
||||
info!(
|
||||
context,
|
||||
"Transport {transport_id}: Subscribing for push notifications."
|
||||
);
|
||||
let transport_id = self.transport_id();
|
||||
|
||||
let old_encrypted_device_token =
|
||||
context.get_config(Config::EncryptedDeviceToken).await?;
|
||||
info!(
|
||||
context,
|
||||
"Transport {transport_id}: Subscribing for push notifications."
|
||||
);
|
||||
|
||||
// Whether we need to update encrypted device token.
|
||||
let device_token_changed = old_encrypted_device_token.is_none()
|
||||
|| context.get_config(Config::DeviceToken).await?.as_ref() != Some(&device_token);
|
||||
|
||||
let new_encrypted_device_token;
|
||||
if device_token_changed {
|
||||
// Reuse the stored ciphertext if the token is unchanged:
|
||||
// encryption gives a different result each time
|
||||
// and the token must be sent byte-identical on every attempt
|
||||
// so the server can deduplicate registrations.
|
||||
let old_device_token = context.get_config(Config::DeviceToken).await?;
|
||||
let encrypted_device_token = match context.get_config(Config::EncryptedDeviceToken).await? {
|
||||
Some(old_encrypted_device_token)
|
||||
if old_device_token.as_ref() == Some(&device_token) =>
|
||||
{
|
||||
old_encrypted_device_token
|
||||
}
|
||||
_ => {
|
||||
let encrypted_device_token = encrypt_device_token(&device_token)
|
||||
.context("Failed to encrypt device token")?;
|
||||
|
||||
// We expect that the server supporting `XDELTAPUSH` capability
|
||||
// has non-synchronizing literals support as well:
|
||||
// <https://www.rfc-editor.org/rfc/rfc7888>.
|
||||
let encrypted_device_token_len = encrypted_device_token.len();
|
||||
|
||||
// Store device token saved on the server
|
||||
// to prevent storing duplicate tokens.
|
||||
// The server cannot deduplicate on its own
|
||||
// because encryption gives a different
|
||||
// result each time.
|
||||
context
|
||||
.set_config_internal(Config::DeviceToken, Some(&device_token))
|
||||
.await?;
|
||||
@@ -1467,51 +1471,49 @@ impl Session {
|
||||
Some(&encrypted_device_token),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if encrypted_device_token_len <= 4096 {
|
||||
new_encrypted_device_token = Some(encrypted_device_token);
|
||||
} else {
|
||||
// If Apple or Google (FCM) gives us a very large token,
|
||||
// do not even try to give it to IMAP servers.
|
||||
//
|
||||
// Limit of 4096 is arbitrarily selected
|
||||
// to be the same as required by LITERAL- IMAP extension.
|
||||
//
|
||||
// Dovecot supports LITERAL+ and non-synchronizing literals
|
||||
// of any length, but there is no reason for tokens
|
||||
// to be that large even after OpenPGP encryption.
|
||||
warn!(context, "Device token is too long for LITERAL-, ignoring.");
|
||||
new_encrypted_device_token = None;
|
||||
}
|
||||
} else {
|
||||
new_encrypted_device_token = old_encrypted_device_token;
|
||||
encrypted_device_token
|
||||
}
|
||||
};
|
||||
|
||||
// Store new encrypted device token on the server
|
||||
// even if it is the same as the old one.
|
||||
if let Some(encrypted_device_token) = new_encrypted_device_token {
|
||||
self.run_command_and_check_ok(&format_setmetadata(
|
||||
"INBOX",
|
||||
&encrypted_device_token,
|
||||
))
|
||||
.await
|
||||
.context("SETMETADATA command failed")?;
|
||||
// If the token cannot be stored we must not retry
|
||||
// on every IMAP loop iteration / register_token invocation.
|
||||
self.push_token_registered = true;
|
||||
|
||||
context.push_subscribed.store(true, Ordering::Relaxed);
|
||||
}
|
||||
// The token is sent as an IMAP quoted string
|
||||
// (<https://www.rfc-editor.org/rfc/rfc3501#section-4.3>),
|
||||
// which carries printable ASCII without double quotes or backslashes.
|
||||
// The encrypted token is `openpgp:` followed by base64
|
||||
// but let's guard against future changes
|
||||
// rather than send a malformed or oversized command.
|
||||
if encrypted_device_token.len() > 4096
|
||||
|| !encrypted_device_token
|
||||
.bytes()
|
||||
.all(|b| b.is_ascii_graphic() && b != b'"' && b != b'\\')
|
||||
{
|
||||
warn!(
|
||||
context,
|
||||
"Device token cannot be stored as metadata, ignoring."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Store the encrypted device token on the server.
|
||||
//
|
||||
// Chatmail relays accept the token and forward it to notifications server
|
||||
// when a new message arrives but other servers may reject or ignore the metadata entry.
|
||||
let command =
|
||||
format!("SETMETADATA \"INBOX\" (/private/devicetoken \"{encrypted_device_token}\")");
|
||||
if let Err(err) = self.run_command_and_check_ok(&command).await {
|
||||
warn!(
|
||||
context,
|
||||
"Transport {transport_id}: Failed to store device token: {err:#}."
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn format_setmetadata(folder: &str, device_token: &str) -> String {
|
||||
let device_token_len = device_token.len();
|
||||
format!(
|
||||
"SETMETADATA \"{folder}\" (/private/devicetoken {{{device_token_len}+}}\r\n{device_token})"
|
||||
)
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Returns success if we successfully set the flag or we otherwise
|
||||
/// think add_flag should not be retried: Disconnection during setting
|
||||
|
||||
@@ -25,12 +25,8 @@ pub(crate) struct Capabilities {
|
||||
/// <https://tools.ietf.org/html/rfc4978>
|
||||
pub can_compress: bool,
|
||||
|
||||
/// True if the server supports XDELTAPUSH capability.
|
||||
/// This capability means setting /private/devicetoken IMAP METADATA
|
||||
/// on the INBOX results in new mail notifications
|
||||
/// via notifications.delta.chat service.
|
||||
/// This is supported by <https://github.com/deltachat/chatmail>
|
||||
pub can_push: bool,
|
||||
/// True if the server advertises the legacy `XDELTAPUSH` capability.
|
||||
pub has_xdeltapush: bool,
|
||||
|
||||
/// True if the server has an XCHATMAIL capability
|
||||
/// indicating that it is a <https://github.com/deltachat/chatmail> server.
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ pub(crate) async fn identify_server(
|
||||
can_check_quota: caps.has_str("QUOTA"),
|
||||
can_metadata: caps.has_str("METADATA"),
|
||||
can_compress: caps.has_str("COMPRESS=DEFLATE"),
|
||||
can_push: caps.has_str("XDELTAPUSH"),
|
||||
has_xdeltapush: caps.has_str("XDELTAPUSH"),
|
||||
is_chatmail: caps.has_str("XCHATMAIL"),
|
||||
server_id,
|
||||
};
|
||||
|
||||
@@ -116,15 +116,3 @@ fn test_uid_grouper() {
|
||||
vec![("INBOX".to_string(), vec![1, 2, 3], "2:3".to_string())]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setmetadata_device_token() {
|
||||
assert_eq!(
|
||||
format_setmetadata("INBOX", "foobarbaz"),
|
||||
"SETMETADATA \"INBOX\" (/private/devicetoken {9+}\r\nfoobarbaz)"
|
||||
);
|
||||
assert_eq!(
|
||||
format_setmetadata("INBOX", "foo\r\nbar\r\nbaz\r\n"),
|
||||
"SETMETADATA \"INBOX\" (/private/devicetoken {15+}\r\nfoo\r\nbar\r\nbaz\r\n)"
|
||||
);
|
||||
}
|
||||
|
||||
+5
-4
@@ -49,6 +49,10 @@ pub(crate) struct Session {
|
||||
/// Should be false if no folder is currently selected.
|
||||
pub new_mail: bool,
|
||||
|
||||
/// True if storing the device token on the relay
|
||||
/// was already attempted on this connection.
|
||||
pub(crate) push_token_registered: bool,
|
||||
|
||||
pub resync_request_sender: async_channel::Sender<()>,
|
||||
}
|
||||
|
||||
@@ -81,6 +85,7 @@ impl Session {
|
||||
selected_mailbox: None,
|
||||
selected_folder_needs_expunge: false,
|
||||
new_mail: false,
|
||||
push_token_registered: false,
|
||||
resync_request_sender,
|
||||
}
|
||||
}
|
||||
@@ -106,10 +111,6 @@ impl Session {
|
||||
self.capabilities.can_metadata
|
||||
}
|
||||
|
||||
pub fn can_push(&self) -> bool {
|
||||
self.capabilities.can_push
|
||||
}
|
||||
|
||||
// Returns true if IMAP server has `XCHATMAIL` capability.
|
||||
pub(crate) fn is_chatmail(&self) -> bool {
|
||||
self.capabilities.is_chatmail
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
clippy::cloned_instead_of_copied,
|
||||
clippy::manual_is_variant_and
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(clippy::large_futures))]
|
||||
#![cfg_attr(not(test), warn(clippy::arithmetic_side_effects))]
|
||||
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
|
||||
#![cfg_attr(not(test), forbid(clippy::string_slice))]
|
||||
|
||||
-25
@@ -7,14 +7,12 @@
|
||||
//! which holds push notification token for the device,
|
||||
//! shared by all accounts.
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use base64::Engine as _;
|
||||
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::key::DcKey;
|
||||
|
||||
/// Manages subscription to Apple Push Notification services.
|
||||
@@ -117,29 +115,6 @@ impl PushSubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
/// Push notification state
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
#[repr(i8)]
|
||||
pub enum NotifyState {
|
||||
/// Not subscribed to push notifications.
|
||||
#[default]
|
||||
NotConnected = 0,
|
||||
|
||||
/// Subscribed to push notifications for new messages.
|
||||
Connected = 2,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Returns push notification subscriber state.
|
||||
pub fn push_state(&self) -> NotifyState {
|
||||
if self.push_subscribed.load(Ordering::Relaxed) {
|
||||
NotifyState::Connected
|
||||
} else {
|
||||
NotifyState::NotConnected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ pub(crate) enum ConnectionSecurity {
|
||||
/// Implicit TLS.
|
||||
Tls,
|
||||
|
||||
// STARTTLS.
|
||||
/// STARTTLS.
|
||||
Starttls,
|
||||
|
||||
/// Plaintext.
|
||||
|
||||
Reference in New Issue
Block a user