Compare commits

..

29 Commits

Author SHA1 Message Date
Septias
c68e25a970 fix tests 2024-11-22 10:26:27 +01:00
Septias
e04c4446d6 fix: Disable setting draft if not in chat 2024-11-20 12:52:33 +01:00
Septias
1b9f3368fa fix: only show draft in message info if self is in chat
This PR adds a check to `get_info` so that `draft` is only added if self
is part of the group determined by `can_send`.

close #6229
2024-11-20 12:28:03 +01:00
gerryfrancis
3b9e6d6ffa Fix type in context.rs 2024-11-19 18:35:42 +01:00
Sebastian Klähn
8f3be764d2 change: Use i.delta.chat in qr codes (#6223)
As discussed in #5467 we want to use `i.delta.chat` in QR codes in favor
of `OPENPGP4FPR:` scheme. This PR does the replacement in
`get_securejoin_qr` which is used in `get_securejoin_qr_svg`.

close #5467
2024-11-19 17:32:42 +01:00
Hocuri
c181db631f feat: Clear config cache in start_io() (#6228)
This is needed for iOS (https://github.com/deltachat/deltachat-ios/pull/2393), see comment in the code. An alternative would be
to add an API `invalidate_config_cache()` or to do nothing and just
assume that things will be fine.
2024-11-19 15:59:05 +00:00
link2xt
c18a476806 refactor: forbid clippy::string_slice 2024-11-18 23:57:57 +00:00
link2xt
3235c8bc9f refactor: forbid clippy::indexing_slicing
It is impossible to allow this in the new code now.
2024-11-18 21:58:48 +00:00
link2xt
a5d336fafc refactor: remove unused allow(clippy::indexing_slicing) from 'truncate' 2024-11-18 21:58:48 +00:00
link2xt
5ebca15502 refactor: get rid of slicing in remove_top_quote 2024-11-18 21:58:48 +00:00
link2xt
d0b945d4ee refactor: remove slicing from remove_bottom_quote 2024-11-18 21:58:48 +00:00
link2xt
d3d2509273 refactor: remove indexing/slicing from parse_message_ids 2024-11-18 21:58:48 +00:00
link2xt
1db6370d6a refactor: remove unused allow(clippy::indexing_slicing) for heuristically_parse_ndn 2024-11-18 21:58:48 +00:00
link2xt
dc58e11d13 refactor: remove indexing/slicing from squash_attachment_parts 2024-11-18 21:58:48 +00:00
link2xt
442e2787c6 refactor: remove indexing/slicing from remove_message_footer 2024-11-18 21:58:48 +00:00
link2xt
7b1fa50fb0 refactor: remove unused allow(clippy::indexing_slicing) 2024-11-18 21:58:48 +00:00
link2xt
2315be2c90 refactor: eliminate indexing in compute_mailinglist_name 2024-11-18 21:58:48 +00:00
link2xt
41478e1e48 refactor: do not use slicing in qr module 2024-11-18 21:58:48 +00:00
link2xt
9e13486143 refactor: don't use slicing in remove_nonstandard_footer 2024-11-18 21:58:48 +00:00
link2xt
06eea7ebe8 refactor: remove unnecessary allow(clippy::indexing_slicing)
clippy::indexing_slicing is already allowed in test builds.
2024-11-18 21:58:48 +00:00
link2xt
514f0296c0 refactor: remove slicing from is_file_in_use
There is a change in behavior for the case
when name is the same as the suffix
(`name_len` == `namespc_len`),
but normally `files_in_use` should not contain empty filenames.
2024-11-18 21:58:48 +00:00
Sebastian Klähn
399716a761 Fix: Dont overwrite equal drafts (#6212)
This PR prevents overwriting drafts when the text and file are the same.

close #6211

---------

Co-authored-by: l <link2xt@testrun.org>
2024-11-17 08:54:50 +00:00
B. Petersen
60163cb121 docs: scanned proxies are added and normalized
there was a bug on iOS before,
that assumed that the proxy needs to be added to the proxy list additionally,
also the normalization was unexpected.
2024-11-16 11:00:42 +01:00
link2xt
e117efa744 ci: ensure flake is formatted 2024-11-15 10:23:36 +00:00
link2xt
7b98274681 fix(deltachat-jsonrpc): do not fail get_draft if draft is deleted 2024-11-14 19:51:43 +00:00
link2xt
ea385fabae fix(deltachat-jsonrpc): do not fail get_chatlist_items_by_entries if the message got deleted
The message may be deleted while chatlist item is loading.
In this case displaying "No messages" is better than failing.
Ideally loading of the chatlist item
should happen in 1 database transaction and
always return some message if chat is not empty,
but this requires large refactoring.
2024-11-14 19:51:43 +00:00
link2xt
3a976a8580 fix: do not fail to load chatlist summary if the message got removed 2024-11-14 19:51:43 +00:00
link2xt
e7a29f0aa7 chore(cargo): update rPGP from 0.13.2 to 0.14.0 2024-11-14 09:31:40 +00:00
bjoern
010b655ee9 api: correct DC_CERTCK_ACCEPT_* values and docs (#6176)
this PR changes `DC_CERTCK_ACCEPT_*` to the same values in cffi as rust
does. and regards the same values as deprecated afterwards

there is some confusion about what is deprecated and what not, see
https://github.com/deltachat/deltachat-android/issues/3408

iOS needs to be adapted as it was following the docs in the CFFI before,
same desktop. both need to be graceful on reading and strict on writing.

~~**this PR is considered harmful,** so we should not merge that in
during 1.48 release, there is no urgency, things are fine (wondering if
it isn't even worth the effort, however, having different values and
deprecations is a call for trouble in the future ...)~~

---------

Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
2024-11-13 16:46:32 +00:00
43 changed files with 802 additions and 435 deletions

22
.github/workflows/nix.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Test Nix flake
on:
pull_request:
push:
branches:
- main
jobs:
format:
name: check flake formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix fmt
# Check that formatting does not change anything.
- run: git diff --exit-code

115
Cargo.lock generated
View File

@@ -53,6 +53,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "aes-kw"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c"
dependencies = [
"aes",
]
[[package]]
name = "ahash"
version = "0.8.11"
@@ -1215,7 +1224,7 @@ dependencies = [
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"fiat-crypto 0.2.6",
"rustc_version",
"subtle",
"zeroize",
@@ -1760,6 +1769,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ed448-goldilocks"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79"
dependencies = [
"fiat-crypto 0.1.20",
"hex",
"subtle",
]
[[package]]
name = "either"
version = "1.10.0"
@@ -2125,6 +2145,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "fiat-crypto"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
[[package]]
name = "fiat-crypto"
version = "0.2.6"
@@ -3095,7 +3121,7 @@ dependencies = [
"netlink-packet-route",
"netlink-sys",
"netwatch",
"num_enum",
"num_enum 0.7.3",
"once_cell",
"parking_lot",
"pin-project",
@@ -3850,13 +3876,34 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
"num_enum_derive 0.5.11",
]
[[package]]
name = "num_enum"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
dependencies = [
"num_enum_derive",
"num_enum_derive 0.7.3",
]
[[package]]
name = "num_enum_derive"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
@@ -3865,7 +3912,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
"proc-macro-crate",
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.86",
@@ -4162,14 +4209,15 @@ dependencies = [
[[package]]
name = "pgp"
version = "0.13.2"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6c842436d5fa2b59eac1e9b3d142b50bfff99c1744c816b1f4c2ac55a20754"
checksum = "49bb5f77aaf8ae1ed6fe63387ad513b10cd44716fd053ecc227b9493c096cdb2"
dependencies = [
"aes",
"aes-gcm",
"aes-kw",
"argon2",
"base64 0.22.1",
"base64 0.21.7",
"bitfield",
"block-padding",
"blowfish",
@@ -4185,6 +4233,7 @@ dependencies = [
"crc24",
"curve25519-dalek",
"derive_builder",
"derive_more",
"des",
"digest",
"dsa",
@@ -4204,7 +4253,7 @@ dependencies = [
"nom",
"num-bigint-dig",
"num-traits",
"num_enum",
"num_enum 0.5.11",
"ocb3",
"p256",
"p384",
@@ -4221,6 +4270,7 @@ dependencies = [
"thiserror",
"twofish",
"x25519-dalek",
"x448",
"zeroize",
]
@@ -4437,7 +4487,7 @@ dependencies = [
"iroh-metrics",
"libc",
"netwatch",
"num_enum",
"num_enum 0.7.3",
"rand 0.8.5",
"serde",
"smallvec",
@@ -4539,13 +4589,23 @@ dependencies = [
"elliptic-curve",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit 0.19.15",
]
[[package]]
name = "proc-macro-crate"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
dependencies = [
"toml_edit",
"toml_edit 0.22.20",
]
[[package]]
@@ -6339,7 +6399,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
"toml_edit 0.22.20",
]
[[package]]
@@ -6351,6 +6411,17 @@ dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"toml_datetime",
"winnow 0.5.40",
]
[[package]]
name = "toml_edit"
version = "0.22.20"
@@ -6361,7 +6432,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
"winnow 0.6.18",
]
[[package]]
@@ -7095,6 +7166,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.6.18"
@@ -7150,6 +7230,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "x448"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb"
dependencies = [
"ed448-goldilocks",
"hex",
"rand_core 0.5.1",
]
[[package]]
name = "x509-parser"
version = "0.16.0"

View File

@@ -76,7 +76,7 @@ num-traits = { workspace = true }
once_cell = { workspace = true }
parking_lot = "0.12"
percent-encoding = "2.3"
pgp = { version = "0.13.2", default-features = false }
pgp = { version = "0.14.0", default-features = false }
pin-project = "1"
qrcodegen = "1.7.0"
quick-xml = "0.37"

View File

@@ -15,7 +15,8 @@
clippy::explicit_into_iter_loop,
clippy::cloned_instead_of_copied
)]
#![cfg_attr(not(test), warn(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::string_slice))]
#![allow(
clippy::match_bool,
clippy::mixed_read_write_in_expression,

View File

@@ -2541,6 +2541,8 @@ void dc_stop_ongoing_process (dc_context_t* context);
* - DC_QR_PROXY with dc_lot_t::text1=address:
* ask the user if they want to use the given proxy.
* if so, call dc_set_config_from_qr() and restart I/O.
* On success, dc_get_config(context, "proxy_url")
* will contain the new proxy in normalized form as the first element.
*
* - DC_QR_ADDR with dc_lot_t::id=Contact ID:
* e-mail address scanned, optionally, a draft message could be set in
@@ -5709,8 +5711,14 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_CERTCK_STRICT 1
/**
* Accept invalid certificates, including self-signed ones
* or having incorrect hostname.
* Accept certificates that are expired, self-signed
* or not valid for the server hostname.
*/
#define DC_CERTCK_ACCEPT_INVALID 2
/**
* For API compatibility only: Treat this as DC_CERTCK_ACCEPT_INVALID on reading.
* Must not be written.
*/
#define DC_CERTCK_ACCEPT_INVALID_CERTIFICATES 3

View File

@@ -1135,9 +1135,11 @@ impl CommandApi {
async fn get_message(&self, account_id: u32, msg_id: u32) -> Result<MessageObject> {
let ctx = self.get_context(account_id).await?;
let msg_id = MsgId::new(msg_id);
MessageObject::from_msg_id(&ctx, msg_id)
let message_object = MessageObject::from_msg_id(&ctx, msg_id)
.await
.with_context(|| format!("Failed to load message {msg_id} for account {account_id}"))
.with_context(|| format!("Failed to load message {msg_id} for account {account_id}"))?
.with_context(|| format!("Message {msg_id} does not exist for account {account_id}"))?;
Ok(message_object)
}
async fn get_message_html(&self, account_id: u32, message_id: u32) -> Result<Option<String>> {
@@ -1161,7 +1163,10 @@ impl CommandApi {
messages.insert(
message_id,
match message_result {
Ok(message) => MessageLoadResult::Message(message),
Ok(Some(message)) => MessageLoadResult::Message(message),
Ok(None) => MessageLoadResult::LoadingError {
error: "Message does not exist".to_string(),
},
Err(error) => MessageLoadResult::LoadingError {
error: format!("{error:#}"),
},
@@ -1999,9 +2004,7 @@ impl CommandApi {
async fn get_draft(&self, account_id: u32, chat_id: u32) -> Result<Option<MessageObject>> {
let ctx = self.get_context(account_id).await?;
if let Some(draft) = ChatId::new(chat_id).get_draft(&ctx).await? {
Ok(Some(
MessageObject::from_msg_id(&ctx, draft.get_id()).await?,
))
Ok(MessageObject::from_msg_id(&ctx, draft.get_id()).await?)
} else {
Ok(None)
}
@@ -2170,7 +2173,9 @@ impl CommandApi {
.await?;
}
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message).await?;
let message = MessageObject::from_msg_id(&ctx, msg_id).await?;
let message = MessageObject::from_msg_id(&ctx, msg_id)
.await?
.context("Just sent message does not exist")?;
Ok((msg_id.to_u32(), message))
}

View File

@@ -88,11 +88,17 @@ pub(crate) async fn get_chat_list_item_by_id(
let (last_updated, message_type) = match last_msgid {
Some(id) => {
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
(
Some(last_message.get_timestamp() * 1000),
Some(last_message.get_viewtype().into()),
)
if let Some(last_message) =
deltachat::message::Message::load_from_db_optional(ctx, id).await?
{
(
Some(last_message.get_timestamp() * 1000),
Some(last_message.get_viewtype().into()),
)
} else {
// Message may be deleted by the time we try to load it.
(None, None)
}
}
None => (None, None),
};

View File

@@ -112,8 +112,10 @@ enum MessageQuote {
}
impl MessageObject {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
let message = Message::load_from_db(context, msg_id).await?;
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Option<Self>> {
let Some(message) = Message::load_from_db_optional(context, msg_id).await? else {
return Ok(None);
};
let sender_contact = Contact::get_by_id(context, message.get_from_id())
.await
@@ -183,7 +185,7 @@ impl MessageObject {
.map(Into::into)
.collect();
Ok(MessageObject {
let message_object = MessageObject {
id: msg_id.to_u32(),
chat_id: message.get_chat_id().to_u32(),
from_id: message.get_from_id().to_u32(),
@@ -244,7 +246,8 @@ impl MessageObject {
reactions,
vcard_contact: vcard_contacts.first().cloned(),
})
};
Ok(Some(message_object))
}
}

View File

@@ -1,4 +1,6 @@
#![recursion_limit = "256"]
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::string_slice))]
pub mod api;
pub use yerpc;

View File

@@ -33,10 +33,14 @@ skip = [
{ name = "event-listener", version = "2.5.3" },
{ name = "event-listener", version = "4.0.3" },
{ name = "fastrand", version = "1.9.0" },
{ name = "fiat-crypto", version = "0.1.20" },
{ name = "futures-lite", version = "1.13.0" },
{ name = "getrandom", version = "<0.2" },
{ name = "http", version = "0.2.12" },
{ name = "nix", version = "0.26.4" },
{ name = "num_enum_derive", version = "0.5.11" },
{ name = "num_enum", version = "0.5.11" },
{ name = "proc-macro-crate", version = "1.3.1" },
{ name = "quick-error", version = "<2.0" },
{ name = "rand_chacha", version = "<0.3" },
{ name = "rand_core", version = "<0.6" },
@@ -47,6 +51,7 @@ skip = [
{ name = "sync_wrapper", version = "0.1.2" },
{ name = "syn", version = "1.0.109" },
{ name = "time", version = "<0.3" },
{ name = "toml_edit", version = "0.19.15" },
{ name = "wasi", version = "<0.11" },
{ name = "windows_aarch64_gnullvm", version = "<0.52" },
{ name = "windows_aarch64_msvc", version = "<0.52" },
@@ -59,6 +64,7 @@ skip = [
{ name = "windows_x86_64_gnullvm", version = "<0.52" },
{ name = "windows_x86_64_gnu", version = "<0.52" },
{ name = "windows_x86_64_msvc", version = "<0.52" },
{ name = "winnow", version = "0.5.40" },
{ name = "winreg", version = "0.50.0" },
]

369
flake.nix
View File

@@ -8,18 +8,8 @@
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
android.url = "github:tadfisher/android-nixpkgs";
};
outputs =
{ self
, nixpkgs
, flake-utils
, nix-filter
, naersk
, fenix
, android
,
}:
flake-utils.lib.eachDefaultSystem (
system:
outputs = { self, nixpkgs, flake-utils, nix-filter, naersk, fenix, android }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs.stdenv) isDarwin;
@@ -183,17 +173,15 @@
# Use DWARF-2 instead of SJLJ for exception handling.
winCC = pkgsWin32.buildPackages.wrapCC (
(pkgsWin32.buildPackages.gcc-unwrapped.override
{
({
threadsCross = {
model = "win32";
package = null;
};
}).overrideAttrs (oldAttr: {
configureFlags =
oldAttr.configureFlags
++ [
"--disable-sjlj-exceptions --with-dwarf2"
];
})).overrideAttrs (oldAttr: {
configureFlags = oldAttr.configureFlags ++ [
"--disable-sjlj-exceptions --with-dwarf2"
];
})
);
in
@@ -337,204 +325,212 @@
{
"deltachat-repl-${arch}" = mkCrossRustPackage arch "deltachat-repl";
"deltachat-rpc-server-${arch}" = rpc-server;
"deltachat-rpc-server-${arch}-wheel" = pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-${arch}-wheel";
version = manifest.version;
src = nix-filter.lib {
root = ./.;
include = [
"scripts/wheel-rpc-server.py"
"deltachat-rpc-server/README.md"
"LICENSE"
"Cargo.toml"
"deltachat-rpc-server-${arch}-wheel" =
pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-${arch}-wheel";
version = manifest.version;
src = nix-filter.lib {
root = ./.;
include = [
"scripts/wheel-rpc-server.py"
"deltachat-rpc-server/README.md"
"LICENSE"
"Cargo.toml"
];
};
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildInputs = [
rpc-server
];
buildPhase = ''
mkdir tmp
cp ${rpc-server}/bin/deltachat-rpc-server tmp/deltachat-rpc-server
python3 scripts/wheel-rpc-server.py ${arch} tmp/deltachat-rpc-server
'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
};
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildInputs = [
rpc-server
];
buildPhase = ''
mkdir tmp
cp ${rpc-server}/bin/deltachat-rpc-server tmp/deltachat-rpc-server
python3 scripts/wheel-rpc-server.py ${arch} tmp/deltachat-rpc-server
'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
};
};
in
{
formatter = pkgs.nixpkgs-fmt;
packages =
mkRustPackages "aarch64-linux"
// mkRustPackages "i686-linux"
// mkRustPackages "x86_64-linux"
// mkRustPackages "armv7l-linux"
// mkRustPackages "armv6l-linux"
// mkAndroidPackages "armeabi-v7a"
// mkAndroidPackages "arm64-v8a"
// mkAndroidPackages "x86"
// mkAndroidPackages "x86_64"
// rec {
mkRustPackages "aarch64-linux" //
mkRustPackages "i686-linux" //
mkRustPackages "x86_64-linux" //
mkRustPackages "armv7l-linux" //
mkRustPackages "armv6l-linux" //
mkAndroidPackages "armeabi-v7a" //
mkAndroidPackages "arm64-v8a" //
mkAndroidPackages "x86" //
mkAndroidPackages "x86_64" // rec {
# Run with `nix run .#deltachat-repl foo.db`.
deltachat-repl = mkRustPackage "deltachat-repl";
deltachat-rpc-server = mkRustPackage "deltachat-rpc-server";
deltachat-repl-win64 = mkWin64RustPackage "deltachat-repl";
deltachat-rpc-server-win64 = mkWin64RustPackage "deltachat-rpc-server";
deltachat-rpc-server-win64-wheel = pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-win64-wheel";
version = manifest.version;
src = nix-filter.lib {
root = ./.;
include = [
"scripts/wheel-rpc-server.py"
"deltachat-rpc-server/README.md"
"LICENSE"
"Cargo.toml"
deltachat-rpc-server-win64-wheel =
pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-win64-wheel";
version = manifest.version;
src = nix-filter.lib {
root = ./.;
include = [
"scripts/wheel-rpc-server.py"
"deltachat-rpc-server/README.md"
"LICENSE"
"Cargo.toml"
];
};
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildInputs = [
deltachat-rpc-server-win64
];
buildPhase = ''
mkdir tmp
cp ${deltachat-rpc-server-win64}/bin/deltachat-rpc-server.exe tmp/deltachat-rpc-server.exe
python3 scripts/wheel-rpc-server.py win64 tmp/deltachat-rpc-server.exe
'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
};
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildInputs = [
deltachat-rpc-server-win64
];
buildPhase = ''
mkdir tmp
cp ${deltachat-rpc-server-win64}/bin/deltachat-rpc-server.exe tmp/deltachat-rpc-server.exe
python3 scripts/wheel-rpc-server.py win64 tmp/deltachat-rpc-server.exe
'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
};
deltachat-repl-win32 = mkWin32RustPackage "deltachat-repl";
deltachat-rpc-server-win32 = mkWin32RustPackage "deltachat-rpc-server";
deltachat-rpc-server-win32-wheel = pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-win32-wheel";
version = manifest.version;
src = nix-filter.lib {
root = ./.;
include = [
"scripts/wheel-rpc-server.py"
"deltachat-rpc-server/README.md"
"LICENSE"
"Cargo.toml"
deltachat-rpc-server-win32-wheel =
pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-win32-wheel";
version = manifest.version;
src = nix-filter.lib {
root = ./.;
include = [
"scripts/wheel-rpc-server.py"
"deltachat-rpc-server/README.md"
"LICENSE"
"Cargo.toml"
];
};
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildInputs = [
deltachat-rpc-server-win32
];
buildPhase = ''
mkdir tmp
cp ${deltachat-rpc-server-win32}/bin/deltachat-rpc-server.exe tmp/deltachat-rpc-server.exe
python3 scripts/wheel-rpc-server.py win32 tmp/deltachat-rpc-server.exe
'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
};
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildInputs = [
deltachat-rpc-server-win32
];
buildPhase = ''
mkdir tmp
cp ${deltachat-rpc-server-win32}/bin/deltachat-rpc-server.exe tmp/deltachat-rpc-server.exe
python3 scripts/wheel-rpc-server.py win32 tmp/deltachat-rpc-server.exe
'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
};
# Run `nix build .#docs` to get C docs generated in `./result/`.
docs = pkgs.stdenv.mkDerivation {
pname = "docs";
version = manifest.version;
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [ pkgs.doxygen ];
buildPhase = ''scripts/run-doxygen.sh'';
installPhase = ''mkdir -p $out; cp -av deltachat-ffi/html deltachat-ffi/xml $out'';
};
docs =
pkgs.stdenv.mkDerivation {
pname = "docs";
version = manifest.version;
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [ pkgs.doxygen ];
buildPhase = ''scripts/run-doxygen.sh'';
installPhase = ''mkdir -p $out; cp -av deltachat-ffi/html deltachat-ffi/xml $out'';
};
libdeltachat = pkgs.stdenv.mkDerivation {
pname = "libdeltachat";
version = manifest.version;
src = rustSrc;
cargoDeps = pkgs.rustPlatform.importCargoLock cargoLock;
libdeltachat =
pkgs.stdenv.mkDerivation {
pname = "libdeltachat";
version = manifest.version;
src = rustSrc;
cargoDeps = pkgs.rustPlatform.importCargoLock cargoLock;
nativeBuildInputs = [
pkgs.perl # Needed to build vendored OpenSSL.
pkgs.cmake
pkgs.rustPlatform.cargoSetupHook
pkgs.cargo
];
buildInputs = pkgs.lib.optionals isDarwin [
pkgs.darwin.apple_sdk.frameworks.CoreFoundation
pkgs.darwin.apple_sdk.frameworks.Security
pkgs.darwin.apple_sdk.frameworks.SystemConfiguration
pkgs.libiconv
];
nativeBuildInputs = [
pkgs.perl # Needed to build vendored OpenSSL.
pkgs.cmake
pkgs.rustPlatform.cargoSetupHook
pkgs.cargo
];
buildInputs = pkgs.lib.optionals isDarwin [
pkgs.darwin.apple_sdk.frameworks.CoreFoundation
pkgs.darwin.apple_sdk.frameworks.Security
pkgs.darwin.apple_sdk.frameworks.SystemConfiguration
pkgs.libiconv
];
postInstall = ''
substituteInPlace $out/include/deltachat.h \
--replace __FILE__ '"${placeholder "out"}/include/deltachat.h"'
'';
};
postInstall = ''
substituteInPlace $out/include/deltachat.h \
--replace __FILE__ '"${placeholder "out"}/include/deltachat.h"'
'';
};
# Source package for deltachat-rpc-server.
# Fake package that downloads Linux version,
# needed to install deltachat-rpc-server on Android with `pip`.
deltachat-rpc-server-source = pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-source";
version = manifest.version;
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat-rpc-server-${manifest.version}.tar.gz'';
installPhase = ''mkdir -p $out; cp -av deltachat-rpc-server-${manifest.version}.tar.gz $out'';
};
deltachat-rpc-server-source =
pkgs.stdenv.mkDerivation {
pname = "deltachat-rpc-server-source";
version = manifest.version;
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [
pkgs.python3
pkgs.python3Packages.wheel
];
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat-rpc-server-${manifest.version}.tar.gz'';
installPhase = ''mkdir -p $out; cp -av deltachat-rpc-server-${manifest.version}.tar.gz $out'';
};
deltachat-rpc-client = pkgs.python3Packages.buildPythonPackage {
pname = "deltachat-rpc-client";
version = manifest.version;
src = pkgs.lib.cleanSource ./deltachat-rpc-client;
format = "pyproject";
propagatedBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.python3Packages.imap-tools
];
};
deltachat-rpc-client =
pkgs.python3Packages.buildPythonPackage {
pname = "deltachat-rpc-client";
version = manifest.version;
src = pkgs.lib.cleanSource ./deltachat-rpc-client;
format = "pyproject";
propagatedBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.python3Packages.imap-tools
];
};
deltachat-python = pkgs.python3Packages.buildPythonPackage {
pname = "deltachat-python";
version = manifest.version;
src = pkgs.lib.cleanSource ./python;
format = "pyproject";
buildInputs = [
libdeltachat
];
nativeBuildInputs = [
pkgs.pkg-config
];
propagatedBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.python3Packages.pkgconfig
pkgs.python3Packages.cffi
pkgs.python3Packages.imap-tools
pkgs.python3Packages.pluggy
pkgs.python3Packages.requests
];
};
python-docs = pkgs.stdenv.mkDerivation {
pname = "docs";
version = manifest.version;
src = pkgs.lib.cleanSource ./.;
buildInputs = [
deltachat-python
deltachat-rpc-client
pkgs.python3Packages.breathe
pkgs.python3Packages.sphinx_rtd_theme
];
nativeBuildInputs = [ pkgs.sphinx ];
buildPhase = ''sphinx-build -b html -a python/doc/ dist/html'';
installPhase = ''mkdir -p $out; cp -av dist/html $out'';
};
deltachat-python =
pkgs.python3Packages.buildPythonPackage {
pname = "deltachat-python";
version = manifest.version;
src = pkgs.lib.cleanSource ./python;
format = "pyproject";
buildInputs = [
libdeltachat
];
nativeBuildInputs = [
pkgs.pkg-config
];
propagatedBuildInputs = [
pkgs.python3Packages.setuptools
pkgs.python3Packages.pkgconfig
pkgs.python3Packages.cffi
pkgs.python3Packages.imap-tools
pkgs.python3Packages.pluggy
pkgs.python3Packages.requests
];
};
python-docs =
pkgs.stdenv.mkDerivation {
pname = "docs";
version = manifest.version;
src = pkgs.lib.cleanSource ./.;
buildInputs = [
deltachat-python
deltachat-rpc-client
pkgs.python3Packages.breathe
pkgs.python3Packages.sphinx_rtd_theme
];
nativeBuildInputs = [ pkgs.sphinx ];
buildPhase = ''sphinx-build -b html -a python/doc/ dist/html'';
installPhase = ''mkdir -p $out; cp -av dist/html $out'';
};
};
devShells.default =
@@ -545,6 +541,7 @@
};
in
pkgs.mkShell {
buildInputs = with pkgs; [
(fenix.packages.${system}.complete.withComponents [
"cargo"

View File

@@ -8,6 +8,8 @@
//! is assumed to be set to "no".
//!
//! For received messages, DelSp parameter is honoured.
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::string_slice))]
/// Wraps line to 72 characters using format=flowed soft breaks.
///

View File

@@ -1,6 +1,7 @@
// Generated!
module.exports = {
DC_CERTCK_ACCEPT_INVALID: 2,
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES: 3,
DC_CERTCK_AUTO: 0,
DC_CERTCK_STRICT: 1,

View File

@@ -1,6 +1,7 @@
// Generated!
export enum C {
DC_CERTCK_ACCEPT_INVALID = 2,
DC_CERTCK_ACCEPT_INVALID_CERTIFICATES = 3,
DC_CERTCK_AUTO = 0,
DC_CERTCK_STRICT = 1,

View File

@@ -705,7 +705,7 @@ class TestOfflineChat:
ac1 = acfactory.get_pseudo_configured_account()
ac2 = acfactory.get_pseudo_configured_account()
qr = ac1.get_setup_contact_qr()
assert qr.startswith("OPENPGP4FPR:")
assert qr.startswith("https://i.delta.chat")
res = ac2.check_qr(qr)
assert res.is_ask_verifycontact()
assert not res.is_ask_verifygroup()

View File

@@ -260,7 +260,6 @@ fn parse_authservid_candidates_config(config: &Option<String>) -> BTreeSet<&str>
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use tokio::fs;
use tokio::io::AsyncReadExt;

View File

@@ -1,11 +1,11 @@
//! # Chat module.
use std::cmp;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use std::{cmp, convert};
use anyhow::{anyhow, bail, ensure, Context as _, Result};
use deltachat_contact_tools::{sanitize_bidi_characters, sanitize_single_line, ContactAddress};
@@ -809,6 +809,14 @@ impl ChatId {
///
/// Passing `None` as message just deletes the draft
pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
let self_in_chat = self.is_self_in_chat(context).await?;
if !self_in_chat {
warn!(
context,
"Not setting draft because self is not member of the chat"
);
return Ok(());
}
if self.is_special() {
return Ok(());
}
@@ -875,6 +883,22 @@ impl ChatId {
> 0)
}
async fn get_chat_type(self, context: &Context) -> Result<Chattype> {
context
.sql
.query_get_value("SELECT type FROM chats WHERE id=?;", (self,))
.await
.map(|res| res.ok_or(anyhow!("Can't get type for char")))
.and_then(convert::identity)
}
async fn is_self_in_chat(self, context: &Context) -> Result<bool> {
match self.get_chat_type(context).await? {
Chattype::Single | Chattype::Broadcast | Chattype::Mailinglist => Ok(true),
Chattype::Group => is_contact_in_chat(context, self, ContactId::SELF).await,
}
}
/// Set provided message as draft message for specified chat.
/// Returns true if the draft was added or updated in place.
async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
@@ -922,24 +946,27 @@ impl ChatId {
&& old_draft.chat_id == self
&& old_draft.state == MessageState::OutDraft
{
context
.sql
.execute(
"UPDATE msgs
SET timestamp=?,type=?,txt=?,txt_normalized=?,param=?,mime_in_reply_to=?
WHERE id=?;",
(
time(),
msg.viewtype,
&msg.text,
message::normalize_text(&msg.text),
msg.param.to_string(),
msg.in_reply_to.as_deref().unwrap_or_default(),
msg.id,
),
)
.await?;
return Ok(true);
let affected_rows = context
.sql.execute(
"UPDATE msgs
SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
WHERE id=?7
AND (type <> ?2
OR txt <> ?3
OR txt_normalized <> ?4
OR param <> ?5
OR mime_in_reply_to <> ?6);",
(
time(),
msg.viewtype,
&msg.text,
message::normalize_text(&msg.text),
msg.param.to_string(),
msg.in_reply_to.as_deref().unwrap_or_default(),
msg.id,
),
).await?;
return Ok(affected_rows > 0);
}
}
}
@@ -1835,8 +1862,9 @@ impl Chat {
/// This is somewhat experimental, even more so than the rest of
/// deltachat, and the data returned is still subject to change.
pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
let self_in_chat = self.is_self_in_chat(context).await?;
let draft = match self.id.get_draft(context).await? {
Some(message) => message.text,
Some(message) if self_in_chat => message.text,
_ => String::new(),
};
Ok(ChatInfo {
@@ -4891,6 +4919,70 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_draft_only_in_accesible_chat_summary() -> Result<()> {
let mut t = TestContextManager::new();
let alice = t.alice().await;
let bob = t.bob().await;
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "chat").await?;
let bob_contact = Contact::create(&alice, "Bob", "bob@example.net").await?;
add_contact_to_chat(&alice, chat_id, bob_contact).await?;
let bob_chat_id = bob
.recv_msg(&alice.send_text(chat_id, "hello bob").await)
.await
.chat_id;
bob_chat_id.accept(&bob).await?;
// Set draft and assure it is in chat info.
let draft = String::from("I'm gonna send this!!");
bob_chat_id
.set_draft(&bob, Some(&mut Message::new_text(draft.clone())))
.await?;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, draft);
// Alice removes bob, so draft is not shown in chat info.
remove_contact_from_chat(&alice, chat_id, bob_contact).await?;
bob.recv_msg(&alice.pop_sent_msg().await).await;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
assert!(!chat.can_send(&bob).await?);
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, String::from(""));
// Alice re-adds bob, so draft is shown again.
add_contact_to_chat(&alice, chat_id, bob_contact).await?;
let bob_chat_id = bob
.recv_msg(&alice.send_text(chat_id, "hello again, bob").await)
.await
.chat_id;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
assert!(chat.can_send(&bob).await?);
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, draft);
// Bob leaves group so draft is not shown.
remove_contact_from_chat(&bob, bob_chat_id, ContactId::SELF).await?;
let chat = Chat::load_from_db(&bob, bob_chat_id).await?;
assert!(!chat.can_send(&bob).await?);
let info = chat.get_info(&bob).await?;
assert_eq!(info.draft, String::from(""));
// Bob can not set a draft if not in chat.
bob_chat_id
.set_draft(
&bob,
Some(&mut Message::new_text(String::from(
"I'm gonna send this fr fr!!",
))),
)
.await?;
assert_eq!(bob_chat_id.get_draft(&bob).await?.unwrap().text, draft);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_change_quotes_on_reused_message_object() -> Result<()> {
let t = TestContext::new_alice().await;
@@ -7696,4 +7788,19 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_do_not_overwrite_draft() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let mut msg = Message::new_text("This is a draft message".to_string());
let self_chat = alice.get_self_chat().await.id;
self_chat.set_draft(&alice, Some(&mut msg)).await.unwrap();
let draft1 = self_chat.get_draft(&alice).await?.unwrap();
SystemTime::shift(Duration::from_secs(1));
self_chat.set_draft(&alice, Some(&mut msg)).await.unwrap();
let draft2 = self_chat.get_draft(&alice).await?.unwrap();
assert_eq!(draft1.timestamp_sort, draft2.timestamp_sort);
Ok(())
}
}

View File

@@ -394,25 +394,32 @@ impl Chatlist {
&chat_loaded
};
let (lastmsg, lastcontact) = if let Some(lastmsg_id) = lastmsg_id {
let lastmsg = Message::load_from_db(context, lastmsg_id)
let lastmsg = if let Some(lastmsg_id) = lastmsg_id {
// Message may be deleted by the time we try to load it,
// so use `load_from_db_optional` instead of `load_from_db`.
Message::load_from_db_optional(context, lastmsg_id)
.await
.context("loading message failed")?;
.context("Loading message failed")?
} else {
None
};
let lastcontact = if let Some(lastmsg) = &lastmsg {
if lastmsg.from_id == ContactId::SELF {
(Some(lastmsg), None)
None
} else {
match chat.typ {
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
let lastcontact = Contact::get_by_id(context, lastmsg.from_id)
.await
.context("loading contact failed")?;
(Some(lastmsg), Some(lastcontact))
Some(lastcontact)
}
Chattype::Single => (Some(lastmsg), None),
Chattype::Single => None,
}
}
} else {
(None, None)
None
};
if chat.id.is_archived_link() {
@@ -761,6 +768,25 @@ mod tests {
assert_eq!(summary.text, "foo: bar test"); // the linebreak should be removed from summary
}
/// Tests that summary does not fail to load
/// if the draft was deleted after loading the chatlist.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_summary_deleted_draft() {
let t = TestContext::new().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat")
.await
.unwrap();
let mut msg = Message::new_text("Foobar".to_string());
chat_id.set_draft(&t, Some(&mut msg)).await.unwrap();
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
chat_id.set_draft(&t, None).await.unwrap();
let summary_res = chats.get_summary(&t, 0, None).await;
assert!(summary_res.is_ok());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_load_broken() {
let t = TestContext::new_bob().await;

View File

@@ -611,8 +611,6 @@ pub enum Error {
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::config::Config;
use crate::login_param::EnteredServerLoginParam;

View File

@@ -272,8 +272,6 @@ pub(crate) async fn moz_autoconfigure(
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
#[test]

View File

@@ -215,8 +215,6 @@ pub(crate) async fn outlk_autodiscover(
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
#[test]

View File

@@ -1252,15 +1252,15 @@ impl Contact {
let fingerprint_self = load_self_public_key(context)
.await?
.fingerprint()
.dc_fingerprint()
.to_string();
let fingerprint_other_verified = peerstate
.peek_key(true)
.map(|k| k.fingerprint().to_string())
.map(|k| k.dc_fingerprint().to_string())
.unwrap_or_default();
let fingerprint_other_unverified = peerstate
.peek_key(false)
.map(|k| k.fingerprint().to_string())
.map(|k| k.dc_fingerprint().to_string())
.unwrap_or_default();
if addr < peerstate.addr {
cat_fingerprint(&mut ret, &addr, &fingerprint_self, "");

View File

@@ -10,6 +10,7 @@ use std::time::Duration;
use anyhow::{bail, ensure, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
use pgp::types::PublicKeyTrait;
use pgp::SignedPublicKey;
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, Notify, OnceCell, RwLock};
@@ -471,6 +472,14 @@ impl Context {
// Allow at least 1 message every second + a burst of 3.
*lock = Ratelimit::new(Duration::new(3, 0), 3.0);
}
// The next line is mainly for iOS:
// iOS starts a separate process for receiving notifications and if the user concurrently
// starts the app, the UI process opens the database but waits with calling start_io()
// until the notifications process finishes.
// Now, some configs may have changed, so, we need to invalidate the cache.
self.sql.config_cache.write().await.clear();
self.scheduler.start(self.clone()).await;
}
@@ -781,7 +790,7 @@ impl Context {
.count("SELECT COUNT(*) FROM acpeerstates;", ())
.await?;
let fingerprint_str = match load_self_public_key(self).await {
Ok(key) => key.fingerprint().hex(),
Ok(key) => key.dc_fingerprint().hex(),
Err(err) => format!("<key failure: {err}>"),
};
@@ -1177,7 +1186,7 @@ impl Context {
EncryptPreference::Mutual,
&public_key,
);
let fingerprint = public_key.fingerprint();
let fingerprint = public_key.dc_fingerprint();
peerstate.set_verified(public_key, fingerprint, "".to_string())?;
peerstate.save_to_db(&self.sql).await?;
chat_id
@@ -2063,4 +2072,41 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_cache_is_cleared_when_io_is_started() -> Result<()> {
let alice = TestContext::new_alice().await;
assert_eq!(
alice.get_config(Config::ShowEmails).await?,
Some("2".to_string())
);
// Change the config circumventing the cache
// This simulates what the notification plugin on iOS might do
// because it runs in a different process
alice
.sql
.execute(
"INSERT OR REPLACE INTO config (keyname, value) VALUES ('show_emails', '0')",
(),
)
.await?;
// Alice's Delta Chat doesn't know about it yet:
assert_eq!(
alice.get_config(Config::ShowEmails).await?,
Some("2".to_string())
);
// Starting IO will fail of course because no server settings are configured,
// but it should invalidate the caches:
alice.start_io().await;
assert_eq!(
alice.get_config(Config::ShowEmails).await?,
Some("0".to_string())
);
Ok(())
}
}

View File

@@ -182,7 +182,7 @@ pub(crate) async fn get_autocrypt_peerstate(
// if the fingerprint is verified.
peerstate = Peerstate::from_verified_fingerprint_or_addr(
context,
&header.public_key.fingerprint(),
&header.public_key.dc_fingerprint(),
from,
)
.await?;

View File

@@ -303,12 +303,12 @@ Sent with my Delta Chat Messenger: https://delta.chat";
last_seen_autocrypt: 14,
prefer_encrypt,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.dc_fingerprint()),
gossip_key: Some(pub_key.clone()),
gossip_timestamp: 15,
gossip_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key_fingerprint: Some(pub_key.dc_fingerprint()),
verified_key: Some(pub_key.clone()),
verified_key_fingerprint: Some(pub_key.fingerprint()),
verified_key_fingerprint: Some(pub_key.dc_fingerprint()),
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,

View File

@@ -4,7 +4,7 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use ::pgp::types::KeyTrait;
use ::pgp::types::PublicKeyTrait;
use anyhow::{bail, ensure, format_err, Context as _, Result};
use futures::TryStreamExt;
use futures_lite::FutureExt;
@@ -750,7 +750,7 @@ where
true => "private",
};
let id = id.map_or("default".into(), |i| i.to_string());
let fp = DcKey::fingerprint(key).hex();
let fp = key.dc_fingerprint().hex();
format!("{kind}-key-{addr}-{id}-{fp}.asc")
};
let path = dir.join(&file_name);
@@ -878,7 +878,7 @@ mod tests {
.unwrap()
.strip_suffix(".asc")
.unwrap();
assert_eq!(fingerprint, DcKey::fingerprint(&key).hex());
assert_eq!(fingerprint, key.dc_fingerprint().hex());
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
let filename = format!("{blobdir}/{filename}");
let bytes = tokio::fs::read(&filename).await.unwrap();

View File

@@ -11,7 +11,8 @@ use num_traits::FromPrimitive;
use pgp::composed::Deserializable;
pub use pgp::composed::{SignedPublicKey, SignedSecretKey};
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
use pgp::types::{PublicKeyTrait, SecretKeyTrait};
use rand::thread_rng;
use tokio::runtime::Handle;
use crate::config::Config;
@@ -26,7 +27,7 @@ use crate::tools::{self, time_elapsed};
/// This trait is implemented for rPGP's [SignedPublicKey] and
/// [SignedSecretKey] types and makes working with them a little
/// easier in the deltachat world.
pub(crate) trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
pub(crate) trait DcKey: Serialize + Deserializable + PublicKeyTrait + Clone {
/// Create a key from some bytes.
fn from_slice(bytes: &[u8]) -> Result<Self> {
Ok(<Self as Deserializable>::from_bytes(Cursor::new(bytes))?)
@@ -93,8 +94,8 @@ pub(crate) trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
fn to_asc(&self, header: Option<(&str, &str)>) -> String;
/// The fingerprint for the key.
fn fingerprint(&self) -> Fingerprint {
Fingerprint::new(KeyTrait::fingerprint(self))
fn dc_fingerprint(&self) -> Fingerprint {
PublicKeyTrait::fingerprint(self).into()
}
fn is_private() -> bool;
@@ -233,7 +234,8 @@ impl DcSecretKey for SignedSecretKey {
fn split_public_key(&self) -> Result<SignedPublicKey> {
self.verify()?;
let unsigned_pubkey = SecretKeyTrait::public_key(self);
let signed_pubkey = unsigned_pubkey.sign(self, || "".into())?;
let mut rng = thread_rng();
let signed_pubkey = unsigned_pubkey.sign(&mut rng, self, || "".into())?;
Ok(signed_pubkey)
}
}
@@ -395,6 +397,12 @@ impl Fingerprint {
}
}
impl From<pgp::types::Fingerprint> for Fingerprint {
fn from(fingerprint: pgp::types::Fingerprint) -> Fingerprint {
Self::new(fingerprint.as_bytes().into())
}
}
impl fmt::Debug for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Fingerprint")

View File

@@ -16,7 +16,8 @@
clippy::explicit_into_iter_loop,
clippy::cloned_instead_of_copied
)]
#![cfg_attr(not(test), warn(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
#![cfg_attr(not(test), forbid(clippy::string_slice))]
#![allow(
clippy::match_bool,
clippy::mixed_read_write_in_expression,

View File

@@ -880,8 +880,6 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::config::Config;
use crate::message::MessageState;

View File

@@ -2274,7 +2274,6 @@ mod tests {
let msg = message.as_string();
let header_end = msg.find("Hi").unwrap();
#[allow(clippy::indexing_slicing)]
let headers = msg[0..header_end].trim();
assert!(!headers.lines().any(|l| l.trim().is_empty()));

View File

@@ -665,11 +665,13 @@ impl MimeMessage {
/// Delta Chat sends attachments, such as images, in two-part messages, with the first message
/// containing a description. If such a message is detected, text from the first part can be
/// moved to the second part, and the first part dropped.
#[allow(clippy::indexing_slicing)]
fn squash_attachment_parts(&mut self) {
if let [textpart, filepart] = &self.parts[..] {
let need_drop = textpart.typ == Viewtype::Text
&& match filepart.typ {
if self.parts.len() == 2
&& self.parts.first().map(|textpart| textpart.typ) == Some(Viewtype::Text)
&& self
.parts
.get(1)
.map_or(false, |filepart| match filepart.typ {
Viewtype::Image
| Viewtype::Gif
| Viewtype::Sticker
@@ -680,24 +682,24 @@ impl MimeMessage {
| Viewtype::File
| Viewtype::Webxdc => true,
Viewtype::Unknown | Viewtype::Text | Viewtype::VideochatInvitation => false,
};
})
{
let mut parts = std::mem::take(&mut self.parts);
let Some(mut filepart) = parts.pop() else {
// Should never happen.
return;
};
let Some(textpart) = parts.pop() else {
// Should never happen.
return;
};
if need_drop {
let mut filepart = self.parts.swap_remove(1);
// insert new one
filepart.msg.clone_from(&self.parts[0].msg);
if let Some(quote) = self.parts[0].param.get(Param::Quote) {
filepart.param.set(Param::Quote, quote);
}
// forget the one we use now
self.parts[0].msg = "".to_string();
// swap new with old
self.parts.push(filepart); // push to the end
let _ = self.parts.swap_remove(0); // drops first element, replacing it with the last one in O(1)
filepart.msg.clone_from(&textpart.msg);
if let Some(quote) = textpart.param.get(Param::Quote) {
filepart.param.set(Param::Quote, quote);
}
self.parts = vec![filepart];
}
}
@@ -1751,7 +1753,6 @@ impl MimeMessage {
/// Some providers like GMX and Yahoo do not send standard NDNs (Non Delivery notifications).
/// If you improve heuristics here you might also have to change prefetch_should_download() in imap/mod.rs.
/// Also you should add a test in receive_imf.rs (there already are lots of test_parse_ndn_* tests).
#[allow(clippy::indexing_slicing)]
async fn heuristically_parse_ndn(&mut self, context: &Context) {
let maybe_ndn = if let Some(from) = self.get_header(HeaderDef::From_) {
let from = from.to_ascii_lowercase();
@@ -1914,18 +1915,17 @@ pub(crate) struct DeliveryReport {
pub failure: bool,
}
#[allow(clippy::indexing_slicing)]
pub(crate) fn parse_message_ids(ids: &str) -> Vec<String> {
// take care with mailparse::msgidparse() that is pretty untolerant eg. wrt missing `<` or `>`
let mut msgids = Vec::new();
for id in ids.split_whitespace() {
let mut id = id.to_string();
if id.starts_with('<') {
id = id[1..].to_string();
}
if id.ends_with('>') {
id = id[..id.len() - 1].to_string();
}
if let Some(id_without_prefix) = id.strip_prefix('<') {
id = id_without_prefix.to_string();
};
if let Some(id_without_suffix) = id.strip_suffix('>') {
id = id_without_suffix.to_string();
};
if !id.is_empty() {
msgids.push(id);
}
@@ -2362,8 +2362,6 @@ async fn ndn_maybe_add_info_msg(
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use mailparse::ParsedMail;
use super::*;

View File

@@ -121,7 +121,7 @@ impl Peerstate {
last_seen_autocrypt: last_seen,
prefer_encrypt,
public_key: Some(public_key.clone()),
public_key_fingerprint: Some(public_key.fingerprint()),
public_key_fingerprint: Some(public_key.dc_fingerprint()),
gossip_key: None,
gossip_key_fingerprint: None,
gossip_timestamp: 0,
@@ -153,7 +153,7 @@ impl Peerstate {
public_key: None,
public_key_fingerprint: None,
gossip_key: Some(gossip_header.public_key.clone()),
gossip_key_fingerprint: Some(gossip_header.public_key.fingerprint()),
gossip_key_fingerprint: Some(gossip_header.public_key.dc_fingerprint()),
gossip_timestamp: message_time,
verified_key: None,
verified_key_fingerprint: None,
@@ -308,7 +308,7 @@ impl Peerstate {
pub fn recalc_fingerprint(&mut self) {
if let Some(ref public_key) = self.public_key {
let old_public_fingerprint = self.public_key_fingerprint.take();
self.public_key_fingerprint = Some(public_key.fingerprint());
self.public_key_fingerprint = Some(public_key.dc_fingerprint());
if old_public_fingerprint.is_some()
&& old_public_fingerprint != self.public_key_fingerprint
@@ -319,7 +319,7 @@ impl Peerstate {
if let Some(ref gossip_key) = self.gossip_key {
let old_gossip_fingerprint = self.gossip_key_fingerprint.take();
self.gossip_key_fingerprint = Some(gossip_key.fingerprint());
self.gossip_key_fingerprint = Some(gossip_key.dc_fingerprint());
if old_gossip_fingerprint.is_none()
|| self.gossip_key_fingerprint.is_none()
@@ -506,7 +506,7 @@ impl Peerstate {
fingerprint: Fingerprint,
verifier: String,
) -> Result<()> {
if key.fingerprint() == fingerprint {
if key.dc_fingerprint() == fingerprint {
self.verified_key = Some(key);
self.verified_key_fingerprint = Some(fingerprint);
self.verifier = Some(verifier);
@@ -524,7 +524,7 @@ impl Peerstate {
/// do nothing to avoid overwriting secondary verified key
/// which may be different.
pub fn set_secondary_verified_key(&mut self, gossip_key: SignedPublicKey, verifier: String) {
let fingerprint = gossip_key.fingerprint();
let fingerprint = gossip_key.dc_fingerprint();
if self.verified_key_fingerprint.as_ref() != Some(&fingerprint) {
self.secondary_verified_key = Some(gossip_key);
self.secondary_verified_key_fingerprint = Some(fingerprint);
@@ -888,12 +888,12 @@ mod tests {
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.dc_fingerprint()),
gossip_key: Some(pub_key.clone()),
gossip_timestamp: 12,
gossip_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key_fingerprint: Some(pub_key.dc_fingerprint()),
verified_key: Some(pub_key.clone()),
verified_key_fingerprint: Some(pub_key.fingerprint()),
verified_key_fingerprint: Some(pub_key.dc_fingerprint()),
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
@@ -913,7 +913,7 @@ mod tests {
.expect("no peerstate found in the database");
assert_eq!(peerstate, peerstate_new);
let peerstate_new2 = Peerstate::from_fingerprint(&ctx.ctx, &pub_key.fingerprint())
let peerstate_new2 = Peerstate::from_fingerprint(&ctx.ctx, &pub_key.dc_fingerprint())
.await
.expect("failed to load peerstate from db")
.expect("no peerstate found in the database");
@@ -932,7 +932,7 @@ mod tests {
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.dc_fingerprint()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,
@@ -969,7 +969,7 @@ mod tests {
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.dc_fingerprint()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,

View File

@@ -14,7 +14,7 @@ use pgp::composed::{
use pgp::crypto::ecc_curve::ECCCurve;
use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::types::{CompressionAlgorithm, KeyTrait, Mpi, PublicKeyTrait, StringToKey};
use pgp::types::{CompressionAlgorithm, PublicKeyTrait, SignatureBytes, StringToKey};
use rand::{thread_rng, CryptoRng, Rng};
use tokio::runtime::Handle;
@@ -41,8 +41,15 @@ enum SignedPublicKeyOrSubkey<'a> {
Subkey(&'a SignedPublicSubKey),
}
impl KeyTrait for SignedPublicKeyOrSubkey<'_> {
fn fingerprint(&self) -> Vec<u8> {
impl PublicKeyTrait for SignedPublicKeyOrSubkey<'_> {
fn version(&self) -> pgp::types::KeyVersion {
match self {
Self::Key(k) => k.version(),
Self::Subkey(k) => k.version(),
}
}
fn fingerprint(&self) -> pgp::types::Fingerprint {
match self {
Self::Key(k) => k.fingerprint(),
Self::Subkey(k) => k.fingerprint(),
@@ -62,14 +69,26 @@ impl KeyTrait for SignedPublicKeyOrSubkey<'_> {
Self::Subkey(k) => k.algorithm(),
}
}
}
impl PublicKeyTrait for SignedPublicKeyOrSubkey<'_> {
fn created_at(&self) -> &chrono::DateTime<chrono::Utc> {
match self {
Self::Key(k) => k.created_at(),
Self::Subkey(k) => k.created_at(),
}
}
fn expiration(&self) -> Option<u16> {
match self {
Self::Key(k) => k.expiration(),
Self::Subkey(k) => k.expiration(),
}
}
fn verify_signature(
&self,
hash: HashAlgorithm,
data: &[u8],
sig: &[Mpi],
sig: &SignatureBytes,
) -> pgp::errors::Result<()> {
match self {
Self::Key(k) => k.verify_signature(hash, data, sig),
@@ -79,19 +98,27 @@ impl PublicKeyTrait for SignedPublicKeyOrSubkey<'_> {
fn encrypt<R: Rng + CryptoRng>(
&self,
rng: &mut R,
rng: R,
plain: &[u8],
) -> pgp::errors::Result<Vec<Mpi>> {
typ: pgp::types::EskType,
) -> pgp::errors::Result<pgp::types::PkeskBytes> {
match self {
Self::Key(k) => k.encrypt(rng, plain),
Self::Subkey(k) => k.encrypt(rng, plain),
Self::Key(k) => k.encrypt(rng, plain, typ),
Self::Subkey(k) => k.encrypt(rng, plain, typ),
}
}
fn to_writer_old(&self, writer: &mut impl io::Write) -> pgp::errors::Result<()> {
fn serialize_for_hashing(&self, writer: &mut impl io::Write) -> pgp::errors::Result<()> {
match self {
Self::Key(k) => k.to_writer_old(writer),
Self::Subkey(k) => k.to_writer_old(writer),
Self::Key(k) => k.serialize_for_hashing(writer),
Self::Subkey(k) => k.serialize_for_hashing(writer),
}
}
fn public_params(&self) -> &pgp::types::PublicParams {
match self {
Self::Key(k) => k.public_params(),
Self::Subkey(k) => k.public_params(),
}
}
}
@@ -160,9 +187,10 @@ pub(crate) fn create_keypair(addr: EmailAddress, keygen_type: KeyGenType) -> Res
let (signing_key_type, encryption_key_type) = match keygen_type {
KeyGenType::Rsa2048 => (PgpKeyType::Rsa(2048), PgpKeyType::Rsa(2048)),
KeyGenType::Rsa4096 => (PgpKeyType::Rsa(4096), PgpKeyType::Rsa(4096)),
KeyGenType::Ed25519 | KeyGenType::Default => {
(PgpKeyType::EdDSA, PgpKeyType::ECDH(ECCCurve::Curve25519))
}
KeyGenType::Ed25519 | KeyGenType::Default => (
PgpKeyType::EdDSALegacy,
PgpKeyType::ECDH(ECCCurve::Curve25519),
),
};
let user_id = format!("<{addr}>");
@@ -199,10 +227,11 @@ pub(crate) fn create_keypair(addr: EmailAddress, keygen_type: KeyGenType) -> Res
.build()
.context("failed to build key parameters")?;
let mut rng = thread_rng();
let secret_key = key_params
.generate()
.generate(&mut rng)
.context("failed to generate the key")?
.sign(|| "".into())
.sign(&mut rng, || "".into())
.context("failed to sign secret key")?;
secret_key
.verify()
@@ -261,15 +290,19 @@ pub async fn pk_encrypt(
let mut rng = thread_rng();
let encrypted_msg = if let Some(ref skey) = private_key_for_signing {
let signed_msg = lit_msg.sign(skey, || "".into(), HASH_ALGORITHM)?;
let signed_msg = lit_msg.sign(&mut rng, skey, || "".into(), HASH_ALGORITHM)?;
let compressed_msg = if compress {
signed_msg.compress(CompressionAlgorithm::ZLIB)?
} else {
signed_msg
};
compressed_msg.encrypt_to_keys(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)?
compressed_msg.encrypt_to_keys_seipdv1(
&mut rng,
SYMMETRIC_KEY_ALGORITHM,
&pkeys_refs,
)?
} else {
lit_msg.encrypt_to_keys(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)?
lit_msg.encrypt_to_keys_seipdv1(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)?
};
let encoded_msg = encrypted_msg.to_armored_string(Default::default())?;
@@ -284,7 +317,9 @@ pub fn pk_calc_signature(
plain: &[u8],
private_key_for_signing: &SignedSecretKey,
) -> Result<String> {
let mut rng = thread_rng();
let msg = Message::new_literal_bytes("", plain).sign(
&mut rng,
private_key_for_signing,
|| "".into(),
HASH_ALGORITHM,
@@ -328,7 +363,7 @@ pub fn valid_signature_fingerprints(
if let signed_msg @ pgp::composed::Message::Signed { .. } = msg {
for pkey in public_keys_for_validation {
if signed_msg.verify(&pkey.primary_key).is_ok() {
let fp = DcKey::fingerprint(pkey);
let fp = pkey.dc_fingerprint();
ret_signature_fingerprints.insert(fp);
}
}
@@ -355,7 +390,7 @@ pub fn pk_validate(
for pkey in public_keys_for_validation {
if standalone_signature.verify(pkey, content).is_ok() {
let fp = DcKey::fingerprint(pkey);
let fp = pkey.dc_fingerprint();
ret.insert(fp);
}
}
@@ -370,8 +405,12 @@ pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
tokio::task::spawn_blocking(move || {
let mut rng = thread_rng();
let s2k = StringToKey::new_default(&mut rng);
let msg =
lit_msg.encrypt_with_password(&mut rng, s2k, SYMMETRIC_KEY_ALGORITHM, || passphrase)?;
let msg = lit_msg.encrypt_with_password_seipdv1(
&mut rng,
s2k,
SYMMETRIC_KEY_ALGORITHM,
|| passphrase,
)?;
let encoded_msg = msg.to_armored_string(Default::default())?;

View File

@@ -288,8 +288,6 @@ pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::test_utils::TestContext;

View File

@@ -367,9 +367,10 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
#[allow(clippy::indexing_slicing)]
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
let payload = &qr[OPENPGP4FPR_SCHEME.len()..];
let payload = qr
.get(OPENPGP4FPR_SCHEME.len()..)
.context("Invalid OPENPGP4FPR scheme")?;
// macOS and iOS sometimes replace the # with %23 (uri encode it), we should be able to parse this wrong format too.
// see issue https://github.com/deltachat/deltachat-core-rust/issues/1969 for more info
@@ -546,7 +547,7 @@ async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result<
fn decode_account(qr: &str) -> Result<Qr> {
let payload = qr
.get(DCACCOUNT_SCHEME.len()..)
.context("invalid DCACCOUNT payload")?;
.context("Invalid DCACCOUNT payload")?;
let url = url::Url::parse(payload).context("Invalid account URL")?;
if url.scheme() == "http" || url.scheme() == "https" {
Ok(Qr::Account {
@@ -564,7 +565,7 @@ fn decode_account(qr: &str) -> Result<Qr> {
fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
let payload = qr
.get(DCWEBRTC_SCHEME.len()..)
.context("invalid DCWEBRTC payload")?;
.context("Invalid DCWEBRTC payload")?;
let (_type, url) = Message::parse_webrtc_instance(payload);
let url = url::Url::parse(&url).context("Invalid WebRTC instance")?;
@@ -637,7 +638,7 @@ fn decode_shadowsocks_proxy(qr: &str) -> Result<Qr> {
fn decode_backup2(qr: &str) -> Result<Qr> {
let payload = qr
.strip_prefix(DCBACKUP2_SCHEME)
.ok_or_else(|| anyhow!("invalid DCBACKUP scheme"))?;
.ok_or_else(|| anyhow!("Invalid DCBACKUP2 scheme"))?;
let (auth_token, node_addr) = payload
.split_once('&')
.context("Backup QR code has no separator")?;
@@ -668,9 +669,10 @@ struct CreateAccountErrorResponse {
/// take a qr of the type DC_QR_ACCOUNT, parse it's parameters,
/// download additional information from the contained url and set the parameters.
/// on success, a configure::configure() should be able to log in to the account
#[allow(clippy::indexing_slicing)]
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
let url_str = qr
.get(DCACCOUNT_SCHEME.len()..)
.context("Invalid DCACCOUNT scheme")?;
if !url_str.starts_with(HTTPS_SCHEME) {
bail!("DCACCOUNT QR codes must use HTTPS scheme");
@@ -800,15 +802,12 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
/// Extract address for the mailto scheme.
///
/// Scheme: `mailto:addr...?subject=...&body=..`
#[allow(clippy::indexing_slicing)]
async fn decode_mailto(context: &Context, qr: &str) -> Result<Qr> {
let payload = &qr[MAILTO_SCHEME.len()..];
let payload = qr
.get(MAILTO_SCHEME.len()..)
.context("Invalid mailto: scheme")?;
let (addr, query) = if let Some(query_index) = payload.find('?') {
(&payload[..query_index], &payload[query_index + 1..])
} else {
(payload, "")
};
let (addr, query) = payload.split_once('?').unwrap_or((payload, ""));
let param: BTreeMap<&str, &str> = query
.split('&')
@@ -855,16 +854,12 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result<Qr> {
/// Extract address for the smtp scheme.
///
/// Scheme: `SMTP:addr...:subject...:body...`
#[allow(clippy::indexing_slicing)]
async fn decode_smtp(context: &Context, qr: &str) -> Result<Qr> {
let payload = &qr[SMTP_SCHEME.len()..];
let addr = if let Some(query_index) = payload.find(':') {
&payload[..query_index]
} else {
bail!("Invalid SMTP found");
};
let payload = qr.get(SMTP_SCHEME.len()..).context("Invalid SMTP scheme")?;
let (addr, _rest) = payload
.split_once(':')
.context("Invalid SMTP scheme payload")?;
let addr = normalize_address(addr)?;
let name = "";
Qr::from_address(context, name, &addr, None).await
@@ -875,14 +870,13 @@ async fn decode_smtp(context: &Context, qr: &str) -> Result<Qr> {
/// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;`
///
/// There may or may not be linebreaks after the fields.
#[allow(clippy::indexing_slicing)]
async fn decode_matmsg(context: &Context, qr: &str) -> Result<Qr> {
// Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field.
// we ignore this case.
let addr = if let Some(to_index) = qr.find("TO:") {
let addr = qr[to_index + 3..].trim();
let addr = qr.get(to_index + 3..).unwrap_or_default().trim();
if let Some(semi_index) = addr.find(';') {
addr[..semi_index].trim()
addr.get(..semi_index).unwrap_or_default().trim()
} else {
addr
}
@@ -903,7 +897,6 @@ static VCARD_EMAIL_RE: Lazy<regex::Regex> =
/// Extract address for the vcard scheme.
///
/// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;<type>:addr...;`
#[allow(clippy::indexing_slicing)]
async fn decode_vcard(context: &Context, qr: &str) -> Result<Qr> {
let name = VCARD_NAME_RE
.captures(qr)
@@ -915,8 +908,8 @@ async fn decode_vcard(context: &Context, qr: &str) -> Result<Qr> {
})
.unwrap_or_default();
let addr = if let Some(caps) = VCARD_EMAIL_RE.captures(qr) {
normalize_address(caps[2].trim())?
let addr = if let Some(cap) = VCARD_EMAIL_RE.captures(qr).and_then(|caps| caps.get(2)) {
normalize_address(cap.as_str().trim())?
} else {
bail!("Bad e-mail address");
};
@@ -1309,7 +1302,7 @@ mod tests {
last_seen_autocrypt: 1,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.dc_fingerprint()),
gossip_key: None,
gossip_timestamp: 0,
gossip_key_fingerprint: None,
@@ -1340,7 +1333,10 @@ mod tests {
let qr = check_qr(
&ctx.ctx,
&format!("OPENPGP4FPR:{}#a=alice@example.org", pub_key.fingerprint()),
&format!(
"OPENPGP4FPR:{}#a=alice@example.org",
pub_key.dc_fingerprint()
),
)
.await?;
if let Qr::FprOk { contact_id, .. } = qr {

View File

@@ -2465,14 +2465,16 @@ async fn create_or_lookup_mailinglist(
}
}
#[allow(clippy::indexing_slicing)]
fn compute_mailinglist_name(
list_id_header: &str,
listid: &str,
mime_parser: &MimeMessage,
) -> String {
let mut name = match LIST_ID_REGEX.captures(list_id_header) {
Some(cap) => cap[1].trim().to_string(),
let mut name = match LIST_ID_REGEX
.captures(list_id_header)
.and_then(|caps| caps.get(1))
{
Some(cap) => cap.as_str().trim().to_string(),
None => "".to_string(),
};
@@ -2519,8 +2521,11 @@ fn compute_mailinglist_name(
// 51231231231231231231231232869f58.xing.com -> xing.com
static PREFIX_32_CHARS_HEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
if let Some(cap) = PREFIX_32_CHARS_HEX.captures(listid) {
name = cap[2].to_string();
if let Some(cap) = PREFIX_32_CHARS_HEX
.captures(listid)
.and_then(|caps| caps.get(2))
{
name = cap.as_str().to_string();
} else {
name = listid.to_string();
}

View File

@@ -104,7 +104,7 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
context.scheduler.interrupt_inbox().await;
}
format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
"https://i.delta.chat/#{}&a={}&g={}&x={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
&group_name_urlencoded,
@@ -119,7 +119,7 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
context.scheduler.interrupt_inbox().await;
}
format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
"https://i.delta.chat/#{}&a={}&n={}&i={}&s={}",
fingerprint.hex(),
self_addr_urlencoded,
self_name_urlencoded,
@@ -136,7 +136,7 @@ async fn get_self_fingerprint(context: &Context) -> Result<Fingerprint> {
let key = load_self_public_key(context)
.await
.context("Failed to load key")?;
Ok(key.fingerprint())
Ok(key.dc_fingerprint())
}
/// Take a scanned QR-code and do the setup-contact/join-group/invite handshake.
@@ -279,7 +279,6 @@ pub(crate) enum HandshakeMessage {
///
/// When `handle_securejoin_handshake()` is called, the message is not yet filed in the
/// database; this is done by `receive_imf()` later on as needed.
#[allow(clippy::indexing_slicing)]
pub(crate) async fn handle_securejoin_handshake(
context: &Context,
mime_message: &MimeMessage,
@@ -298,9 +297,9 @@ pub(crate) async fn handle_securejoin_handshake(
if !matches!(step, "vg-request" | "vc-request") {
let mut self_found = false;
let self_fingerprint = load_self_public_key(context).await?.fingerprint();
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
for (addr, key) in &mime_message.gossiped_keys {
if key.fingerprint() == self_fingerprint && context.is_self_addr(addr).await? {
if key.dc_fingerprint() == self_fingerprint && context.is_self_addr(addr).await? {
self_found = true;
break;
}
@@ -348,7 +347,7 @@ pub(crate) async fn handle_securejoin_handshake(
send_alice_handshake_msg(
context,
contact_id,
&format!("{}-auth-required", &step[..2]),
&format!("{}-auth-required", &step.get(..2).unwrap_or_default()),
)
.await
.context("failed sending auth-required handshake message")?;
@@ -936,7 +935,10 @@ mod tests {
"vc-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = load_self_public_key(&bob.ctx).await.unwrap().fingerprint();
let bob_fp = load_self_public_key(&bob.ctx)
.await
.unwrap()
.dc_fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -1106,10 +1108,10 @@ mod tests {
last_seen_autocrypt: 10,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(alice_pubkey.clone()),
public_key_fingerprint: Some(alice_pubkey.fingerprint()),
public_key_fingerprint: Some(alice_pubkey.dc_fingerprint()),
gossip_key: Some(alice_pubkey.clone()),
gossip_timestamp: 10,
gossip_key_fingerprint: Some(alice_pubkey.fingerprint()),
gossip_key_fingerprint: Some(alice_pubkey.dc_fingerprint()),
verified_key: None,
verified_key_fingerprint: None,
verifier: None,
@@ -1157,7 +1159,7 @@ mod tests {
"vc-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = load_self_public_key(&bob.ctx).await?.fingerprint();
let bob_fp = load_self_public_key(&bob.ctx).await?.dc_fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()
@@ -1320,7 +1322,7 @@ mod tests {
"vg-request-with-auth"
);
assert!(msg.get_header(HeaderDef::SecureJoinAuth).is_some());
let bob_fp = load_self_public_key(&bob.ctx).await?.fingerprint();
let bob_fp = load_self_public_key(&bob.ctx).await?.dc_fingerprint();
assert_eq!(
*msg.get_header(HeaderDef::SecureJoinFingerprint).unwrap(),
bob_fp.hex()

View File

@@ -389,7 +389,7 @@ async fn send_handshake_message(
msg.param.set_int(Param::GuaranteeE2ee, 1);
// Sends our own fingerprint in the Secure-Join-Fingerprint header.
let bob_fp = load_self_public_key(context).await?.fingerprint();
let bob_fp = load_self_public_key(context).await?.dc_fingerprint();
msg.param.set(Param::Arg3, bob_fp.hex());
// Sends the grpid in the Secure-Join-Group header.

View File

@@ -1,4 +1,5 @@
//! # Simplify incoming plaintext.
use crate::tools::IsNoneOrEmpty;
/// Protects lines starting with `--` against being treated as a footer.
/// for that, we insert a ZERO WIDTH SPACE (ZWSP, 0x200B);
@@ -20,7 +21,6 @@ pub fn escape_message_footer_marks(text: &str) -> String {
/// Returns `(lines, footer_lines)` tuple;
/// `footer_lines` is set to `Some` if the footer was actually removed from `lines`
/// (which is equal to the input array otherwise).
#[allow(clippy::indexing_slicing)]
pub(crate) fn remove_message_footer<'a>(
lines: &'a [&str],
) -> (&'a [&'a str], Option<&'a [&'a str]>) {
@@ -28,14 +28,13 @@ pub(crate) fn remove_message_footer<'a>(
for (ix, &line) in lines.iter().enumerate() {
match line {
// some providers encode `-- ` to `-- =20` which results in `-- `
"-- " | "-- " => return (&lines[..ix], lines.get(ix + 1..)),
"-- " | "-- " => return (lines.get(..ix).unwrap_or(lines), lines.get(ix + 1..)),
// some providers encode `-- ` to `=2D-` which results in only `--`;
// use that only when no other footer is found
// and if the line before is empty and the line after is not empty
"--" => {
if (ix == 0 || lines[ix - 1].is_empty())
&& ix != lines.len() - 1
&& !lines[ix + 1].is_empty()
if (ix == 0 || lines.get(ix.saturating_sub(1)).is_none_or_empty())
&& !lines.get(ix + 1).is_none_or_empty()
{
nearly_standard_footer = Some(ix);
}
@@ -44,7 +43,7 @@ pub(crate) fn remove_message_footer<'a>(
}
}
if let Some(ix) = nearly_standard_footer {
return (&lines[..ix], lines.get(ix + 1..));
return (lines.get(..ix).unwrap_or(lines), lines.get(ix + 1..));
}
(lines, None)
}
@@ -53,7 +52,6 @@ pub(crate) fn remove_message_footer<'a>(
/// Returns `(lines, is_footer_removed)` tuple;
/// `is_footer_removed` is set to `true` if the footer was actually removed from `lines`
/// (which is equal to the input array otherwise).
#[allow(clippy::indexing_slicing)]
fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
for (ix, &line) in lines.iter().enumerate() {
if line == "--"
@@ -63,7 +61,10 @@ fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|| line.starts_with("*****")
|| line.starts_with("~~~~~")
{
return (&lines[..ix], true);
// `get` should always return `Some` here.
if let Some(lines) = lines.get(..ix) {
return (lines, true);
}
}
}
(lines, false)
@@ -173,7 +174,6 @@ fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
}
}
#[allow(clippy::indexing_slicing)]
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>) {
let mut first_quoted_line = lines.len();
let mut last_quoted_line = None;
@@ -188,30 +188,36 @@ fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>)
}
}
if let Some(mut l_last) = last_quoted_line {
let quoted_text = lines[l_last..first_quoted_line]
let quoted_text = lines
.iter()
.take(first_quoted_line)
.skip(l_last)
.map(|s| {
s.strip_prefix('>')
.map_or(*s, |u| u.strip_prefix(' ').unwrap_or(u))
})
.collect::<Vec<&str>>()
.join("\n");
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
if l_last > 1 {
let line = lines[l_last - 1];
if is_quoted_headline(line) {
l_last -= 1
if let Some(line) = lines.get(l_last - 1) {
if is_empty_line(line) {
l_last -= 1
}
}
}
(&lines[..l_last], Some(quoted_text))
if l_last > 1 {
if let Some(line) = lines.get(l_last - 1) {
if is_quoted_headline(line) {
l_last -= 1
}
}
}
(lines.get(..l_last).unwrap_or(lines), Some(quoted_text))
} else {
(lines, None)
}
}
#[allow(clippy::indexing_slicing)]
fn remove_top_quote<'a>(
lines: &'a [&str],
is_chat_message: bool,
@@ -238,10 +244,12 @@ fn remove_top_quote<'a>(
}
if let Some(last_quoted_line) = last_quoted_line {
(
&lines[last_quoted_line + 1..],
lines.get(last_quoted_line + 1..).unwrap_or(lines),
Some(
lines[first_quoted_line..last_quoted_line + 1]
lines
.iter()
.take(last_quoted_line + 1)
.skip(first_quoted_line)
.map(|s| {
s.strip_prefix('>')
.map_or(*s, |u| u.strip_prefix(' ').unwrap_or(u))

View File

@@ -942,15 +942,12 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
Ok(())
}
#[allow(clippy::indexing_slicing)]
fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
let name_to_check = if let Some(namespc) = namespc_opt {
let name_len = name.len();
let namespc_len = namespc.len();
if name_len <= namespc_len || !name.ends_with(namespc) {
let Some(name) = name.strip_suffix(namespc) else {
return false;
}
&name[..name_len - namespc_len]
};
name
} else {
name
};

View File

@@ -1,7 +1,6 @@
//! Utilities to help writing tests.
//!
//! This private module is only compiled for test runs.
#![allow(clippy::indexing_slicing)]
use std::collections::{BTreeMap, HashSet};
use std::fmt::Write;
use std::ops::{Deref, DerefMut};

View File

@@ -1,5 +1,3 @@
#![allow(clippy::indexing_slicing)]
use anyhow::Result;
use crate::chat;

View File

@@ -45,23 +45,29 @@ use crate::stock_str;
/// Shortens a string to a specified length and adds "[...]" to the
/// end of the shortened string.
#[allow(clippy::indexing_slicing)]
pub(crate) fn truncate(buf: &str, approx_chars: usize) -> Cow<str> {
let count = buf.chars().count();
if count > approx_chars + DC_ELLIPSIS.len() {
let end_pos = buf
.char_indices()
.nth(approx_chars)
.map(|(n, _)| n)
.unwrap_or_default();
if count <= approx_chars + DC_ELLIPSIS.len() {
return Cow::Borrowed(buf);
}
let end_pos = buf
.char_indices()
.nth(approx_chars)
.map(|(n, _)| n)
.unwrap_or_default();
if let Some(index) = buf[..end_pos].rfind([' ', '\n']) {
Cow::Owned(format!("{}{}", &buf[..=index], DC_ELLIPSIS))
} else {
Cow::Owned(format!("{}{}", &buf[..end_pos], DC_ELLIPSIS))
}
if let Some(index) = buf.get(..end_pos).and_then(|s| s.rfind([' ', '\n'])) {
Cow::Owned(format!(
"{}{}",
&buf.get(..=index).unwrap_or_default(),
DC_ELLIPSIS
))
} else {
Cow::Borrowed(buf)
Cow::Owned(format!(
"{}{}",
&buf.get(..end_pos).unwrap_or_default(),
DC_ELLIPSIS
))
}
}
@@ -709,8 +715,6 @@ pub(crate) fn inc_and_check<T: PrimInt + AddAssign + std::fmt::Debug>(
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing)]
use chrono::NaiveDate;
use proptest::prelude::*;