Compare commits

..

59 Commits

Author SHA1 Message Date
link2xt
37c771d342 temporarily replace logging with print 2024-05-25 05:52:07 +00:00
Septias
72df9504ad optimize logs 2024-05-25 05:52:07 +00:00
Sebastian Klähn
c68008aa04 feat(peer_channels): call update_endpoints 2024-05-25 05:52:07 +00:00
link2xt
d8f3368b3c chore: fix python lint 2024-05-25 05:51:37 +00:00
link2xt
5755fe7bef test(deltachat-rpc-client): regression test for double gossip subscription 2024-05-25 05:46:55 +00:00
link2xt
4f071e3b31 fix: acquire write lock on iroh_channels before checking for subscribe_loop 2024-05-25 05:46:55 +00:00
holger krekel
f4dfc79808 test(deltachat-rpc-client): add realtime channel tests
Co-authored-by: link2xt <link2xt@testrun.org>
2024-05-25 05:46:55 +00:00
link2xt
518d5bc4c7 refactor: log IMAP connection type on connection failure
Otherwise it is impossible to distinguish between
failure to establish INBOX and DeltaChat folder connections
in the logs.
2024-05-24 00:13:56 +00:00
link2xt
0e1f62a38d build: update iroh to the git version
This fixes the problem with simultaneous connections in iroh-gossip:
<https://github.com/n0-computer/iroh/pull/2318>
2024-05-23 22:58:45 +00:00
iequidoo
af4b59fe0a test: Viewtype::File attachments are sent unchanged and preserve extensions 2024-05-23 17:10:52 -03:00
link2xt
8c3c0484ed fix(@deltachat/stdio-rpc-server): do not set RUST_LOG to "info" by default
`info` enables info level logging for all libraries,
e.g. iroh starts printing very verbose logs
to stderr this way.
2024-05-23 15:50:29 +00:00
link2xt
97828234dd chore(release): prepare for 1.139.5 2024-05-23 13:15:08 +00:00
iequidoo
20e64c71f8 test: "SecureJoin wait" state and info messages 2024-05-23 14:36:13 +02:00
iequidoo
2214d140c3 fix: Don't recode images in Viewtype::File messages (#5617) 2024-05-23 14:35:38 +02:00
link2xt
907d3efcd0 api(deltachat-rpc-client): add Message.send_webxdc_realtime_data() 2024-05-21 22:15:49 +00:00
link2xt
9573e02c32 api(deltachat-rpc-client): add Message.send_webxdc_realtime_advertisement() 2024-05-21 22:15:49 +00:00
Sebastian Klähn
8cb699290a fix: connect to peers that advertise to you 2024-05-21 22:12:23 +00:00
Friedel Ziegelmayer
31d7b4f9ce feat(deltachat-repl): add start-realtime and send-realtime commands 2024-05-21 22:11:22 +00:00
link2xt
2e5ad3f3a0 test(peer_channels): add test_parallel_connect() 2024-05-21 22:10:15 +00:00
holger krekel
5d3d5d23a1 api(deltachat-rpc-client): add EventType.WEBXDC_REALTIME_DATA constant 2024-05-21 22:10:15 +00:00
link2xt
469ff799ad api: add event channel overflow event 2024-05-21 22:05:48 +00:00
link2xt
18f2a09b35 api(deltachat-ffi): make WebXdcRealtimeData data usable in CFFI
Previously only msg_id was returned to CFFI
without any way to get to the actual received data.
2024-05-21 22:05:34 +00:00
link2xt
81f6aec1a0 chore(release): prepare for 1.139.4 2024-05-21 18:09:55 +00:00
iequidoo
ff60605a7f test: import_vcard() updates only the contact's gossip key 2024-05-21 17:40:07 +00:00
iequidoo
7010e80336 fix: make_vcard: Add authname and key for ContactId::SELF 2024-05-21 17:40:07 +00:00
iequidoo
5f790c1dbc fix(contact-tools): parse_vcard: Support \r\n newlines 2024-05-21 17:40:07 +00:00
iequidoo
8c5d8477fb feat: Add import_vcard() (#5202)
Add a function importing contacts from the given vCard.
2024-05-21 17:40:07 +00:00
iequidoo
10fe6929b0 feat: Scale up contact origins to OutgoingTo when sending a message 2024-05-21 17:40:07 +00:00
link2xt
6fc0000c8a fix: do not log warning if iroh relay metadata is NIL 2024-05-21 15:24:08 +00:00
Sebastian Klähn
e84a5589df nix: add nextest (#5610)
Has some unrelated change that I add the whole .vscode folder to
.gitignore which I also need.
2024-05-21 08:18:05 +00:00
link2xt
e7d9ff12ec chore(release): prepare for 1.139.3 2024-05-20 18:19:27 +00:00
link2xt
607f5959ab fix: always convert absolute paths to relative in accounts.toml
Even if the path does not start with
the directory containing the config,
we want to keep only the last component.
2024-05-20 17:49:21 +00:00
Simon Laux
11546a1ce9 api!(npm rpc server): change api: don't search in path unless options.takeVersionFromPATH is set to true
-remove `DELTA_CHAT_SKIP_PATH` environment variable
- remove version check / search for dc rpc server in $PATH
- remove `options.skipSearchInPath`
- add `options.takeVersionFromPATH`
2024-05-20 18:55:05 +02:00
Simon Laux
ee671836ca fix: npm rpc: set default options for startDeltaChat
this fixes an "undefined" error
2024-05-20 18:55:05 +02:00
Simon Laux
dd77d32446 fix: log/print exit error of deltachat-rpc-server (#5601)
see also #5599 (this logs that error atleast, but does not fix it yet)

---------

Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
2024-05-20 10:47:36 +00:00
link2xt
b32fb05ab8 fix: ignore event channel overflows
async-broadcast returns Overflowed error once
if channel overflow happened.
Public APIs such as get_next_event JSON-RPC method
are only expecting an error if the channel is closed,
so we should not propagate overflow error outside.
In particular, Delta Chat Desktop
stop receiving events completely if an error
is returned once.
If overflow happens, we should ignore it
and try again until we get an event or an error because
the channel is closed (in case of recv())
or empty (in case of try_recv()).
2024-05-20 10:44:35 +00:00
link2xt
918d87dcb6 refactor: do not try to lookup group in create_or_lookup_group() 2024-05-20 05:20:38 +00:00
link2xt
98ae05ee59 refactor: stop trying to extract chat ID from Message-IDs 2024-05-20 05:20:38 +00:00
link2xt
cff5c064a6 refactor: use let..else in create_or_lookup_group() 2024-05-20 05:20:38 +00:00
link2xt
e9cef4b0ba refactor(receive_imf): only call create_or_lookup_group() with allow_creation=true 2024-05-20 05:20:38 +00:00
link2xt
7f2c8ff53d refactor(receive_imf): remove unnecessary check for is_mdn
If message is an MDN, it is already assigned to the trash chat
by now, so it is enough to check if the message
is not assigned to chat yet.
2024-05-20 05:20:38 +00:00
link2xt
46d6b81058 refactor(receive_imf): do not check for ContactId::UNDEFINED
from_id should not be undefined,
we either should create a contact for it by then
or reject the message.
2024-05-20 05:20:38 +00:00
link2xt
6d59fb49aa feat: replace env_logger with tracing_subscriber
This allows to get iroh logs with
RUST_LOG=iroh_net=trace
2024-05-19 23:22:37 +00:00
Simon Laux
97602f3fd7 fix: sql syntax error in db migration 27 2024-05-19 20:15:22 +02:00
Simon Laux
f17987743e fix: db migration version 59, it contained an sql syntax error 2024-05-19 20:15:22 +02:00
link2xt
5767cce178 fix(mimeparser): take the last header of multiple ones with the same name
If multiple headers with the same name are present,
we must take the last one.
This is the one that is DKIM-signed if
this header is DKIM-signed at all.

Ideally servers should prevent adding
more From, To and Cc headers by oversigning
them, but unfortunately it is not common
and OpenDKIM in its default configuration
does not oversign any headers.
2024-05-18 22:24:39 +00:00
link2xt
20a4bb1a88 api(deltachat-rpc-client): add Account.wait_for_incoming_msg() 2024-05-18 22:24:17 +00:00
link2xt
578f29f215 chore(release): prepare for 1.139.2 2024-05-18 20:58:03 +00:00
link2xt
6c9643e39e build: add repository URL to @deltachat/jsonrpc-client 2024-05-18 20:56:11 +00:00
link2xt
502ae7fd9f chore(release): prepare for 1.139.1 2024-05-18 20:40:34 +00:00
link2xt
8cb527342a ci: set --access public when publishing to npm
Otherwise it fails to upload new packages with provenance.
2024-05-18 20:39:41 +00:00
link2xt
964c943dd9 chore(release): prepare for 1.139.0 2024-05-18 19:42:54 +00:00
Simon Laux
a971ad1f85 npm rpc server: fix example (#5580) 2024-05-18 19:58:44 +02:00
link2xt
e66b9de922 fix: do not mark the message as seen if it has location.kml 2024-05-18 16:09:55 +00:00
adbenitez
5db202169b fix: save override sender displayname for outgoing messages
Co-authored-by: link2xt <link2xt@testrun.org>
2024-05-18 16:08:53 +00:00
link2xt
b292b191ff test(deltachat-rpc-client): test sending vCard 2024-05-18 14:00:57 +00:00
link2xt
450ff411ec api(deltachat-rpc-client): add Chat.send_contact() 2024-05-18 14:00:57 +00:00
link2xt
8de92e54eb api(deltachat-rpc-client): add Contact.make_vcard() 2024-05-18 14:00:57 +00:00
link2xt
d0844c3e62 api(deltachat-rpc-client): add ViewType.VCARD constant 2024-05-18 14:00:57 +00:00
63 changed files with 1331 additions and 848 deletions

View File

@@ -401,6 +401,6 @@ jobs:
working-directory: deltachat-rpc-server/npm-package
run: |
ls -lah platform_package
for platform in *.tgz; do npm publish --provenance "$platform"; done
for platform in *.tgz; do npm publish --provenance "$platform" --access public; done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -33,6 +33,6 @@ jobs:
- name: Publish
working-directory: deltachat-jsonrpc/typescript
run: npm publish --provenance deltachat-jsonrpc-client-*
run: npm publish --provenance deltachat-jsonrpc-client-* --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

2
.gitignore vendored
View File

@@ -50,4 +50,4 @@ result
# direnv
.envrc
.direnv
.direnv

View File

@@ -1,5 +1,134 @@
# Changelog
## [1.139.5] - 2024-05-23
### API-Changes
- deltachat-ffi: Make WebXdcRealtimeData data usable in CFFI.
- Add event channel overflow event.
- deltachat-rpc-client: Add EventType.WEBXDC_REALTIME_DATA constant.
- deltachat-rpc-client: Add Message.send_webxdc_realtime_advertisement().
- deltachat-rpc-client: Add Message.send_webxdc_realtime_data().
### Features / Changes
- deltachat-repl: Add start-realtime and send-realtime commands.
### Fixes
- peer_channels: Connect to peers that advertise to you.
- Don't recode images in `Viewtype::File` messages ([#5617](https://github.com/deltachat/deltachat-core-rust/pull/5617)).
### Tests
- peer_channels: Add test_parallel_connect().
- "SecureJoin wait" state and info messages.
## [1.139.4] - 2024-05-21
### Features / Changes
- Scale up contact origins to OutgoingTo when sending a message.
- Add import_vcard() ([#5202](https://github.com/deltachat/deltachat-core-rust/pull/5202)).
### Fixes
- Do not log warning if iroh relay metadata is NIL.
- contact-tools: Parse_vcard: Support `\r\n` newlines.
- Make_vcard: Add authname and key for ContactId::SELF.
### Other
- nix: Add nextest ([#5610](https://github.com/deltachat/deltachat-core-rust/pull/5610)).
## [1.139.3] - 2024-05-20
### API-Changes
- [**breaking**] @deltachat/stdio-rpc-server: change api: don't search in path unless `options.takeVersionFromPATH` is set to `true`
- @deltachat/stdio-rpc-server: remove `DELTA_CHAT_SKIP_PATH` environment variable
- @deltachat/stdio-rpc-server: remove version check / search for dc rpc server in $PATH
- @deltachat/stdio-rpc-server: remove `options.skipSearchInPath`
- @deltachat/stdio-rpc-server: add `options.takeVersionFromPATH`
- deltachat-rpc-client: Add Account.wait_for_incoming_msg().
### Features / Changes
- Replace env_logger with tracing_subscriber.
### Fixes
- Ignore event channel overflows.
- mimeparser: Take the last header of multiple ones with the same name.
- Db migration version 59, it contained an sql syntax error.
- Sql syntax error in db migration 27.
- Log/print exit error of deltachat-rpc-server ([#5601](https://github.com/deltachat/deltachat-core-rust/pull/5601)).
- @deltachat/stdio-rpc-server: set default options for `startDeltaChat`.
- Always convert absolute paths to relative in accounts.toml.
### Refactor
- receive_imf: Do not check for ContactId::UNDEFINED.
- receive_imf: Remove unnecessary check for is_mdn.
- receive_imf: Only call create_or_lookup_group() with allow_creation=true.
- Use let..else in create_or_lookup_group().
- Stop trying to extract chat ID from Message-IDs.
- Do not try to lookup group in create_or_lookup_group().
## [1.139.2] - 2024-05-18
### Build system
- Add repository URL to @deltachat/jsonrpc-client.
## [1.139.1] - 2024-05-18
### CI
- Set `--access public` when publishing to npm.
## [1.139.0] - 2024-05-18
### Features / Changes
- Ephemeral peer channels ([#5346](https://github.com/deltachat/deltachat-core-rust/pull/5346)).
### Fixes
- Save override sender displayname for outgoing messages.
- Do not mark the message as seen if it has `location.kml`.
- @deltachat/stdio-rpc-server: fix version check when deltachat-rpc-server is found in path ([#5579](https://github.com/deltachat/deltachat-core-rust/pull/5579)).
- @deltachat/stdio-rpc-server: fix local desktop development ([#5583](https://github.com/deltachat/deltachat-core-rust/pull/5583)).
- @deltachat/stdio-rpc-server: rename `shutdown` method to `close` and add `muteStdErr` option to mute the stderr output ([#5588](https://github.com/deltachat/deltachat-core-rust/pull/5588))
- @deltachat/stdio-rpc-server: fix `convert_platform.py`: 32bit `i32` -> `ia32` ([#5589](https://github.com/deltachat/deltachat-core-rust/pull/5589))
- @deltachat/stdio-rpc-server: fix example ([#5580](https://github.com/deltachat/deltachat-core-rust/pull/5580))
### API-Changes
- deltachat-jsonrpc: Return vcard contact directly in MessageObject.
- deltachat-jsonrpc: Add api `migrate_account` and `get_blob_dir` ([#5584](https://github.com/deltachat/deltachat-core-rust/pull/5584)).
- deltachat-rpc-client: Add ViewType.VCARD constant.
- deltachat-rpc-client: Add Contact.make_vcard().
- deltachat-rpc-client: Add Chat.send_contact().
### CI
- Publish @deltachat/jsonrpc-client directly to npm.
- Check that constants are always up-to-date.
### Build system
- nix: Add git-cliff to flake.
- nix: Use rust-analyzer nightly
### Miscellaneous Tasks
- cargo: Downgrade libc from 0.2.154 to 0.2.153.
### Tests
- deltachat-rpc-client: Test sending vCard.
## [1.138.5] - 2024-05-16
### API-Changes
@@ -4165,3 +4294,9 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.138.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.2...v1.138.3
[1.138.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.3...v1.138.4
[1.138.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.4...v1.138.5
[1.139.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.5...v1.139.0
[1.139.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.139.0...v1.139.1
[1.139.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.139.1...v1.139.2
[1.139.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.139.2...v1.139.3
[1.139.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.139.3...v1.139.4
[1.139.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.139.4...v1.139.5

417
Cargo.lock generated
View File

@@ -512,23 +512,6 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "bao-tree"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f7a89a8ee5889d2593ae422ce6e1bb03e48a0e8a16e4fa0882dfcbe7e182ef"
dependencies = [
"bytes",
"futures-lite 2.3.0",
"genawaiter",
"iroh-blake3",
"iroh-io",
"positioned-io",
"range-collections",
"self_cell",
"smallvec",
]
[[package]]
name = "base16ct"
version = "0.1.1"
@@ -577,12 +560,6 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "binary-merge"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab"
[[package]]
name = "bincode"
version = "1.3.3"
@@ -1347,7 +1324,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.138.5"
version = "1.139.5"
dependencies = [
"ansi_term",
"anyhow",
@@ -1395,7 +1372,6 @@ dependencies = [
"percent-encoding",
"pgp",
"pretty_assertions",
"pretty_env_logger",
"proptest",
"qrcodegen",
"quick-xml",
@@ -1404,7 +1380,7 @@ dependencies = [
"rand 0.8.5",
"ratelimit",
"regex",
"reqwest",
"reqwest 0.11.27",
"rusqlite",
"rust-hsluv",
"sanitize-filename",
@@ -1426,8 +1402,6 @@ dependencies = [
"tokio-tar",
"tokio-util",
"toml",
"tracing",
"tracing-subscriber",
"url",
"uuid",
]
@@ -1445,7 +1419,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.138.5"
version = "1.139.5"
dependencies = [
"anyhow",
"async-channel 2.2.1",
@@ -1453,7 +1427,7 @@ dependencies = [
"base64 0.22.1",
"deltachat",
"deltachat-contact-tools",
"env_logger 0.11.3",
"env_logger",
"futures",
"log",
"num-traits",
@@ -1470,27 +1444,26 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.138.5"
version = "1.139.5"
dependencies = [
"ansi_term",
"anyhow",
"deltachat",
"dirs",
"log",
"pretty_env_logger",
"rusqlite",
"rustyline",
"tokio",
"tracing-subscriber",
]
[[package]]
name = "deltachat-rpc-server"
version = "1.138.5"
version = "1.139.5"
dependencies = [
"anyhow",
"deltachat",
"deltachat-jsonrpc",
"env_logger 0.11.3",
"futures-lite 2.3.0",
"log",
"serde",
@@ -1515,7 +1488,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.138.5"
version = "1.139.5"
dependencies = [
"anyhow",
"deltachat",
@@ -1589,7 +1562,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ef71ddb5b3a1f53dee24817c8f70dfa1cb29e804c18d88c228d4bc9c86ee3b9"
dependencies = [
"proc-macro-error 1.0.4",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -2153,19 +2126,6 @@ dependencies = [
"regex",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.11.3"
@@ -2589,8 +2549,6 @@ checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0"
dependencies = [
"futures-core",
"genawaiter-macro",
"genawaiter-proc-macro",
"proc-macro-hack",
]
[[package]]
@@ -2599,19 +2557,6 @@ version = "0.99.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc"
[[package]]
name = "genawaiter-proc-macro"
version = "0.99.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784f84eebc366e15251c4a8c3acee82a6a6f427949776ecb88377362a9621738"
dependencies = [
"proc-macro-error 0.4.12",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -3032,9 +2977,26 @@ dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.28",
"rustls",
"rustls 0.21.11",
"tokio",
"tokio-rustls",
"tokio-rustls 0.24.1",
]
[[package]]
name = "hyper-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.2.0",
"hyper-util",
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
"tower-service",
]
[[package]]
@@ -3057,6 +3019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
@@ -3064,6 +3027,9 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower-service",
"tracing",
]
[[package]]
@@ -3198,15 +3164,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "inplace-vec-builder"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf64c2edc8226891a71f127587a2861b132d2b942310843814d5001d99a1d307"
dependencies = [
"smallvec",
]
[[package]]
name = "instant"
version = "0.1.12"
@@ -3225,7 +3182,7 @@ dependencies = [
"socket2",
"widestring",
"windows-sys 0.48.0",
"winreg",
"winreg 0.50.0",
]
[[package]]
@@ -3260,8 +3217,8 @@ dependencies = [
"rand 0.7.3",
"rcgen 0.10.0",
"ring 0.16.20",
"rustls",
"rustls-webpki",
"rustls 0.21.11",
"rustls-webpki 0.101.7",
"serde",
"serde-error",
"ssh-key 0.5.1",
@@ -3280,18 +3237,18 @@ dependencies = [
[[package]]
name = "iroh-base"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02a1a5323b25a181b1434a44f9f59ebc478d21156cf9bf91aa850ad0d626f833"
version = "0.16.0"
source = "git+https://github.com/n0-computer/iroh?rev=c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e#c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e"
dependencies = [
"aead",
"anyhow",
"bao-tree",
"crypto_box",
"data-encoding",
"derive_more 1.0.0-beta.6",
"ed25519-dalek 2.1.1",
"getrandom 0.2.12",
"hex",
"iroh-blake3",
"once_cell",
"postcard",
"rand 0.8.5",
@@ -3320,9 +3277,8 @@ dependencies = [
[[package]]
name = "iroh-gossip"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11daf47e11d00016eeac662b8b13343d40233764fe4b971e0d6cf15b1c98f0a1"
version = "0.16.0"
source = "git+https://github.com/n0-computer/iroh?rev=c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e#c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e"
dependencies = [
"anyhow",
"bytes",
@@ -3344,22 +3300,10 @@ dependencies = [
"tracing",
]
[[package]]
name = "iroh-io"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d1047ad5ca29ab4ff316b6830d86e7ea52cea54325e4d4a849692e1274b498"
dependencies = [
"bytes",
"futures-lite 2.3.0",
"pin-project",
]
[[package]]
name = "iroh-metrics"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b4f668653628979461eabe56853a694b1eb4713e87ed25f2224618165c0e67"
version = "0.16.0"
source = "git+https://github.com/n0-computer/iroh?rev=c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e#c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e"
dependencies = [
"anyhow",
"erased_set",
@@ -3368,7 +3312,7 @@ dependencies = [
"hyper-util",
"once_cell",
"prometheus-client",
"reqwest",
"reqwest 0.12.4",
"serde",
"struct_iterable",
"time 0.3.34",
@@ -3378,13 +3322,14 @@ dependencies = [
[[package]]
name = "iroh-net"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bea6e221dfbe6301965a5ec9b6bae2c156375a4baafdbdbad7a93c3dcf950d6"
version = "0.16.0"
source = "git+https://github.com/n0-computer/iroh?rev=c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e#c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e"
dependencies = [
"aead",
"anyhow",
"axum",
"backoff",
"base64 0.22.1",
"bytes",
"der 0.7.8",
"derive_more 1.0.0-beta.6",
@@ -3424,11 +3369,11 @@ dependencies = [
"rand 0.8.5",
"rand_core 0.6.4",
"rcgen 0.11.3",
"reqwest",
"reqwest 0.12.4",
"ring 0.17.8",
"rtnetlink",
"rustls",
"rustls-webpki",
"rustls 0.21.11",
"rustls-webpki 0.101.7",
"serde",
"smallvec",
"socket2",
@@ -3438,13 +3383,13 @@ dependencies = [
"thiserror",
"time 0.3.34",
"tokio",
"tokio-rustls",
"tokio-rustls 0.24.1",
"tokio-rustls-acme",
"tokio-util",
"tracing",
"url",
"watchable",
"webpki-roots",
"webpki-roots 0.25.4",
"windows 0.51.1",
"wmi",
"x509-parser 0.15.1",
@@ -3462,7 +3407,7 @@ dependencies = [
"iroh-quinn-udp",
"pin-project-lite",
"rustc-hash",
"rustls",
"rustls 0.21.11",
"thiserror",
"tokio",
"tracing",
@@ -3478,7 +3423,7 @@ dependencies = [
"rand 0.8.5",
"ring 0.16.20",
"rustc-hash",
"rustls",
"rustls 0.21.11",
"rustls-native-certs",
"slab",
"thiserror",
@@ -4509,7 +4454,7 @@ dependencies = [
"bytes",
"ed25519-dalek 2.1.1",
"rand 0.8.5",
"reqwest",
"reqwest 0.11.27",
"self_cell",
"simple-dns",
"thiserror",
@@ -4672,16 +4617,6 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "positioned-io"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccabfeeb89c73adf4081f0dca7f8e28dbda90981a222ceea37f619e93ea6afe9"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "postcard"
version = "1.0.8"
@@ -4762,16 +4697,6 @@ dependencies = [
"yansi",
]
[[package]]
name = "pretty_env_logger"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
dependencies = [
"env_logger 0.10.2",
"log",
]
[[package]]
name = "primeorder"
version = "0.13.6"
@@ -4790,45 +4715,19 @@ dependencies = [
"toml_edit 0.21.1",
]
[[package]]
name = "proc-macro-error"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
dependencies = [
"proc-macro-error-attr 0.4.12",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr 1.0.4",
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn-mid",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
@@ -4840,12 +4739,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.81"
@@ -4960,7 +4853,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"rustls 0.21.11",
"thiserror",
"tokio",
"tracing",
@@ -4976,7 +4869,7 @@ dependencies = [
"rand 0.8.5",
"ring 0.16.20",
"rustc-hash",
"rustls",
"rustls 0.21.11",
"rustls-native-certs",
"slab",
"thiserror",
@@ -5118,18 +5011,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "range-collections"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca9edd21e2db51000ac63eccddabba622f826e631a60be7bade9bd6a76b69537"
dependencies = [
"binary-merge",
"inplace-vec-builder",
"ref-cast",
"smallvec",
]
[[package]]
name = "ratelimit"
version = "1.0.0"
@@ -5228,26 +5109,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "ref-cast"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "regex"
version = "1.10.4"
@@ -5313,7 +5174,7 @@ dependencies = [
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.28",
"hyper-rustls",
"hyper-rustls 0.24.2",
"hyper-tls",
"ipnet",
"js-sys",
@@ -5323,8 +5184,8 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"rustls 0.21.11",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"serde_urlencoded",
@@ -5332,14 +5193,55 @@ dependencies = [
"system-configuration 0.5.1",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-rustls 0.24.1",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
"webpki-roots 0.25.4",
"winreg 0.50.0",
]
[[package]]
name = "reqwest"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-core",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"hyper 1.2.0",
"hyper-rustls 0.26.0",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls 0.22.4",
"rustls-pemfile 2.1.2",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 0.1.2",
"tokio",
"tokio-rustls 0.25.0",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 0.26.1",
"winreg 0.52.0",
]
[[package]]
@@ -5543,10 +5445,24 @@ checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4"
dependencies = [
"log",
"ring 0.17.8",
"rustls-webpki",
"rustls-webpki 0.101.7",
"sct",
]
[[package]]
name = "rustls"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
dependencies = [
"log",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.4",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
@@ -5554,7 +5470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pemfile 1.0.4",
"schannel",
"security-framework",
]
@@ -5568,6 +5484,22 @@ dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
dependencies = [
"base64 0.22.1",
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
@@ -5578,6 +5510,17 @@ dependencies = [
"untrusted 0.9.0",
]
[[package]]
name = "rustls-webpki"
version = "0.102.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e"
dependencies = [
"ring 0.17.8",
"rustls-pki-types",
"untrusted 0.9.0",
]
[[package]]
name = "rustversion"
version = "1.0.14"
@@ -6258,17 +6201,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn-mid"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea305d57546cc8cd04feb14b62ec84bf17f50e3f7b12560d7bfa9265f39d9ed"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
@@ -6384,15 +6316,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "testdir"
version = "0.9.1"
@@ -6571,7 +6494,18 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls",
"rustls 0.21.11",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
]
@@ -6590,16 +6524,16 @@ dependencies = [
"pem 3.0.4",
"proc-macro2",
"rcgen 0.12.1",
"reqwest",
"reqwest 0.11.27",
"ring 0.17.8",
"rustls",
"rustls 0.21.11",
"serde",
"serde_json",
"thiserror",
"tokio",
"tokio-rustls",
"tokio-rustls 0.24.1",
"url",
"webpki-roots",
"webpki-roots 0.25.4",
"x509-parser 0.16.0",
]
@@ -6882,7 +6816,7 @@ checksum = "2835fe6badda3e20a012d19d6593ded0fc11f659d5d5152394061ffbb03b4b04"
dependencies = [
"darling 0.13.4",
"ident_case",
"proc-macro-error 1.0.4",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -7171,6 +7105,15 @@ version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "webpki-roots"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.8"
@@ -7526,6 +7469,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wmi"
version = "0.13.3"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.138.5"
version = "1.139.5"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.77"
@@ -61,8 +61,8 @@ hickory-resolver = "0.24"
humansize = "2"
image = { version = "0.25.1", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
iroh_old = { version = "0.4.2", default-features = false, package = "iroh"}
iroh-net = { git = "https://github.com/link2xt/iroh", branch="link2xt/keep-connection" }
iroh-gossip = { git = "https://github.com/link2xt/iroh", branch="link2xt/keep-connection", features = ["net"] }
iroh-net = { git = "https://github.com/n0-computer/iroh", rev="c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e" }
iroh-gossip = { git = "https://github.com/n0-computer/iroh", rev="c8690a2c6eb5753c4ec6b7e44db72abf09df3c6e", features = ["net"] }
quinn = "0.10.0"
kamadak-exif = "0.5.3"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
@@ -76,7 +76,6 @@ once_cell = { workspace = true }
percent-encoding = "2.3"
parking_lot = "0.12"
pgp = { version = "0.11", default-features = false }
pretty_env_logger = { version = "0.5", optional = true }
qrcodegen = "1.7.0"
quick-xml = "0.31"
quoted_printable = "0.5"
@@ -104,7 +103,6 @@ tokio-util = "0.7.9"
toml = "0.8"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Pin OpenSSL to 3.1 releases.
# OpenSSL 3.2 has a regression tracked at <https://github.com/openssl/openssl/issues/23376>
@@ -113,8 +111,6 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# According to <https://www.openssl.org/policies/releasestrat.html>
# 3.1 branch will be supported until 2025-03-14.
openssl-src = "~300.1"
tracing = "0.1.40"
[dev-dependencies]
ansi_term = "0.12.0"
@@ -122,7 +118,6 @@ anyhow = { version = "1", features = ["backtrace"] } # Enable `backtrace` featur
criterion = { version = "0.5.1", features = ["async_tokio"] }
futures-lite = "2.3.0"
log = "0.4"
pretty_env_logger = "0.5"
proptest = { version = "1", default-features = false, features = ["std"] }
tempfile = "3"
testdir = "0.9.0"
@@ -184,4 +179,4 @@ vendored = [
"async-native-tls/vendored",
"rusqlite/bundled-sqlcipher-vendored-openssl",
"reqwest/native-tls-vendored"
]
]

View File

@@ -155,7 +155,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
}
// Remove line folding, see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2
static NEWLINE_AND_SPACE_OR_TAB: Lazy<Regex> = Lazy::new(|| Regex::new("\n[\t ]").unwrap());
static NEWLINE_AND_SPACE_OR_TAB: Lazy<Regex> = Lazy::new(|| Regex::new("\r?\n[\t ]").unwrap());
let unfolded_lines = NEWLINE_AND_SPACE_OR_TAB.replace_all(vcard, "");
let mut lines = unfolded_lines.lines().peekable();
@@ -643,10 +643,10 @@ END:VCARD
}
#[test]
fn test_android_vcard_with_base64_avatar() {
// This is not an actual base64-encoded avatar, it's just to test the parsing
let contacts = parse_vcard(
"BEGIN:VCARD
fn test_vcard_with_base64_avatar() {
// This is not an actual base64-encoded avatar, it's just to test the parsing.
// This one is Android-like.
let vcard0 = "BEGIN:VCARD
VERSION:2.1
N:;Bob;;;
FN:Bob
@@ -656,13 +656,16 @@ PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU
L8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==
END:VCARD
",
);
assert_eq!(contacts.len(), 1);
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
";
// This one is DOS-like.
let vcard1 = vcard0.replace('\n', "\r\n");
for vcard in [vcard0, vcard1.as_str()] {
let contacts = parse_vcard(vcard);
assert_eq!(contacts.len(), 1);
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.138.5"
version = "1.139.5"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -6284,6 +6284,18 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121
/**
* Data received over an ephemeral peer channel.
*
* @param data1 (int) msg_id
* @param data2 (int) + (char*) binary data.
* length is returned as integer with dc_event_get_data2_int()
* and binary data is returned as dc_event_get_data2_str().
* Binary data must be passed to dc_str_unref() afterwards.
*/
#define DC_EVENT_WEBXDC_REALTIME_DATA 2150
/**
* Tells that the Background fetch was completed (or timed out).
*
@@ -6312,6 +6324,14 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_CHATLIST_ITEM_CHANGED 2301
/**
* Inform that some events have been skipped due to event channel overflow.
*
* @param data1 (int) number of events that have been skipped
*/
#define DC_EVENT_CHANNEL_OVERFLOW 2400
/**
* @}
*/

View File

@@ -566,6 +566,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::AccountsBackgroundFetchDone => 2200,
EventType::ChatlistChanged => 2300,
EventType::ChatlistItemChanged { .. } => 2301,
EventType::EventChannelOverflow { .. } => 2400,
}
}
@@ -624,6 +625,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
EventType::ChatlistItemChanged { chat_id } => {
chat_id.unwrap_or_default().to_u32() as libc::c_int
}
EventType::EventChannelOverflow { n } => *n as libc::c_int,
}
}
@@ -658,13 +660,13 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::ConnectivityChanged
| EventType::WebxdcInstanceDeleted { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::WebxdcRealtimeData { .. }
| EventType::SelfavatarChanged
| EventType::AccountsBackgroundFetchDone
| EventType::ChatlistChanged
| EventType::ChatlistItemChanged { .. }
| EventType::ConfigSynced { .. } => 0,
EventType::ChatModified(_) => 0,
| EventType::ConfigSynced { .. }
| EventType::ChatModified(_)
| EventType::EventChannelOverflow { .. } => 0,
EventType::MsgsChanged { msg_id, .. }
| EventType::ReactionsChanged { msg_id, .. }
| EventType::IncomingMsg { msg_id, .. }
@@ -679,6 +681,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
status_update_serial,
..
} => status_update_serial.to_u32() as libc::c_int,
EventType::WebxdcRealtimeData { data, .. } => data.len() as libc::c_int,
}
}
@@ -725,12 +728,12 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::SelfavatarChanged
| EventType::WebxdcStatusUpdate { .. }
| EventType::WebxdcInstanceDeleted { .. }
| EventType::WebxdcRealtimeData { .. }
| EventType::AccountsBackgroundFetchDone
| EventType::ChatEphemeralTimerModified { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::ChatlistItemChanged { .. }
| EventType::ChatlistChanged => ptr::null_mut(),
| EventType::ChatlistChanged
| EventType::EventChannelOverflow { .. } => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment {
comment.to_c_string().unwrap_or_default().into_raw()
@@ -746,6 +749,11 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
let data2 = key.to_string().to_c_string().unwrap_or_default();
data2.into_raw()
}
EventType::WebxdcRealtimeData { data, .. } => {
let ptr = libc::malloc(data.len());
libc::memcpy(ptr, data.as_ptr() as *mut libc::c_void, data.len());
ptr as *mut libc::c_char
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.138.5"
version = "1.139.5"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"

View File

@@ -1447,7 +1447,7 @@ impl CommandApi {
/// Parses a vCard file located at the given path. Returns contacts in their original order.
async fn parse_vcard(&self, path: String) -> Result<Vec<VcardContact>> {
let vcard = tokio::fs::read(Path::new(&path)).await?;
let vcard = fs::read(Path::new(&path)).await?;
let vcard = str::from_utf8(&vcard)?;
Ok(deltachat_contact_tools::parse_vcard(vcard)
.into_iter()
@@ -1455,6 +1455,20 @@ impl CommandApi {
.collect())
}
/// Imports contacts from a vCard file located at the given path.
///
/// Returns the ids of created/modified contacts in the order they appear in the vCard.
async fn import_vcard(&self, account_id: u32, path: String) -> Result<Vec<u32>> {
let ctx = self.get_context(account_id).await?;
let vcard = tokio::fs::read(Path::new(&path)).await?;
let vcard = str::from_utf8(&vcard)?;
Ok(deltachat::contact::import_vcard(&ctx, vcard)
.await?
.into_iter()
.map(|c| c.to_u32())
.collect())
}
/// Returns a vCard containing contacts with the given ids.
async fn make_vcard(&self, account_id: u32, contacts: Vec<u32>) -> Result<String> {
let ctx = self.get_context(account_id).await?;

View File

@@ -263,6 +263,9 @@ pub enum EventType {
/// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
#[serde(rename_all = "camelCase")]
ChatlistItemChanged { chat_id: Option<u32> },
/// Inform than some events have been skipped due to event channel overflow.
EventChannelOverflow { n: u64 },
}
impl From<CoreEventType> for EventType {
@@ -378,6 +381,7 @@ impl From<CoreEventType> for EventType {
chat_id: chat_id.map(|id| id.to_u32()),
},
CoreEventType::ChatlistChanged => ChatlistChanged,
CoreEventType::EventChannelOverflow { n } => EventChannelOverflow { n },
}
}
}

View File

@@ -32,6 +32,10 @@
"license": "MPL-2.0",
"main": "dist/deltachat.js",
"name": "@deltachat/jsonrpc-client",
"repository": {
"type": "git",
"url": "https://github.com/deltachat/deltachat-core-rust.git"
},
"scripts": {
"build": "run-s generate-bindings extract-constants build:tsc build:bundle build:cjs",
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
@@ -54,5 +58,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.138.5"
"version": "1.139.5"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.138.5"
version = "1.139.5"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/deltachat/deltachat-core-rust"
@@ -11,10 +11,10 @@ anyhow = "1"
deltachat = { path = "..", features = ["internals"]}
dirs = "5"
log = "0.4.21"
pretty_env_logger = "0.5"
rusqlite = "0.31"
rustyline = "14"
tokio = { version = "1.37.0", features = ["fs", "rt-multi-thread", "macros"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[features]
default = ["vendored"]

View File

@@ -32,6 +32,7 @@ use rustyline::{
};
use tokio::fs;
use tokio::runtime::Handle;
use tracing_subscriber::EnvFilter;
mod cmdline;
use self::cmdline::*;
@@ -483,9 +484,10 @@ async fn handle_cmd(
#[tokio::main]
async fn main() -> Result<(), Error> {
pretty_env_logger::formatted_timed_builder()
.parse_default_env()
.filter_module("deltachat_repl", log::LevelFilter::Info)
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::from_default_env().add_directive("deltachat_repl=info".parse()?),
)
.init();
let args = std::env::args().collect();

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "1.138.5"
version = "1.139.5"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",

View File

@@ -297,6 +297,12 @@ class Account:
if event.kind == EventType.INCOMING_MSG:
return event
def wait_for_incoming_msg(self):
"""Wait for incoming message and return it.
Consumes all events before the next incoming message event."""
return self.get_message_by_id(self.wait_for_incoming_msg_event().msg_id)
def wait_for_securejoin_inviter_success(self):
while True:
event = self.wait_for_event()

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import calendar
from dataclasses import dataclass
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Optional, Union
from ._utils import AttrDict
@@ -265,3 +266,11 @@ class Chat:
location["message"] = Message(self.account, location.msg_id)
locations.append(location)
return locations
def send_contact(self, contact: Contact):
"""Send contact to the chat."""
vcard = contact.make_vcard()
with NamedTemporaryFile(suffix=".vcard") as f:
f.write(vcard.encode())
f.flush()
self._rpc.send_msg(self.account.id, self.id, {"viewtype": ViewType.VCARD, "file": f.name})

View File

@@ -115,6 +115,7 @@ class ViewType(str, Enum):
FILE = "File"
VIDEOCHAT_INVITATION = "VideochatInvitation"
WEBXDC = "Webxdc"
VCARD = "Vcard"
class SystemMessageType(str, Enum):

View File

@@ -60,3 +60,6 @@ class Contact:
self.account,
self._rpc.create_chat_by_contact_id(self.account.id, self.id),
)
def make_vcard(self) -> str:
return self._rpc.make_vcard(self.account.id, [self.id])

View File

@@ -2,7 +2,7 @@ import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional, Union
from ._utils import AttrDict
from ._utils import AttrDict, futuremethod
from .const import EventType
from .contact import Contact
@@ -70,3 +70,11 @@ class Message:
event = self.account.wait_for_event()
if event.kind == EventType.MSG_DELIVERED and event.msg_id == self.id:
break
@futuremethod
def send_webxdc_realtime_advertisement(self):
yield self._rpc.send_webxdc_realtime_advertisement.future(self.account.id, self.id)
@futuremethod
def send_webxdc_realtime_data(self, data) -> None:
yield self._rpc.send_webxdc_realtime_data.future(self.account.id, self.id, list(data))

View File

@@ -177,7 +177,8 @@ class Rpc:
account_id = event["contextId"]
queue = self.get_queue(account_id)
event = event["event"]
print("account_id=%d got an event %s" % (account_id, event), file=sys.stderr)
# logging.debug("account_id=%d got an event %s", account_id, event)
print("account_id=%d got an event %s" % (account_id, event))
queue.put(event)
except Exception:
# Log an exception if the event loop dies.

View File

@@ -126,8 +126,7 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
alice.set_config("download_limit", "1")
event = bob.wait_for_incoming_msg_event()
msg = bob.get_message_by_id(event.msg_id)
msg = bob.wait_for_incoming_msg()
chat_id = msg.get_snapshot().chat_id
msg.get_snapshot().chat.accept()
bob.get_chat_by_id(chat_id).send_message(
@@ -135,13 +134,15 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
html=base64.b64encode(os.urandom(300000)).decode("utf-8"),
)
msg_id = alice.wait_for_incoming_msg_event().msg_id
assert alice.get_message_by_id(msg_id).get_snapshot().download_state == const.DownloadState.AVAILABLE
message = alice.wait_for_incoming_msg()
snapshot = message.get_snapshot()
assert snapshot.download_state == const.DownloadState.AVAILABLE
alice.clear_all_events()
chat_id = alice.get_message_by_id(msg_id).get_snapshot().chat_id
alice._rpc.download_full_message(alice.id, msg_id)
snapshot = message.get_snapshot()
chat_id = snapshot.chat_id
alice._rpc.download_full_message(alice.id, message.id)
wait_for_chatlist_specific_item(alice, chat_id)
@@ -177,8 +178,7 @@ def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
alice_chat_bob.send_text("hello")
event = bob.wait_for_incoming_msg_event()
msg = bob.get_message_by_id(event.msg_id)
msg = bob.wait_for_incoming_msg()
bob_chat_id = msg.get_snapshot().chat_id
msg.get_snapshot().chat.accept()
@@ -189,8 +189,7 @@ def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
# make sure alice_second_device already received the message
alice_second_device.wait_for_incoming_msg_event()
event = alice.wait_for_incoming_msg_event()
msg = alice.get_message_by_id(event.msg_id)
msg = alice.wait_for_incoming_msg()
alice_second_device.clear_all_events()
msg.mark_seen()

View File

@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""
Testing webxdc iroh connectivity
If you want to debug iroh at rust-trace/log level set
RUST_LOG=iroh_net=trace,iroh_gossip=trace
"""
import sys
import threading
import time
import pytest
from deltachat_rpc_client import EventType
@pytest.fixture()
def path_to_webxdc(request):
p = request.path.parent.parent.parent.joinpath("test-data/webxdc/chess.xdc")
assert p.exists()
return str(p)
def log(msg):
print()
print("*" * 80 + "\n" + msg + "\n", file=sys.stderr)
print()
def setup_realtime_webxdc(ac1, ac2, path_to_webxdc):
ac1_ac2_chat = ac1.create_chat(ac2)
ac2.create_chat(ac1)
# share a webxdc app between ac1 and ac2
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="play", file=path_to_webxdc)
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
assert ac2_webxdc_msg.get_snapshot().text == "play"
# send iroh announcements simultaneously
log("sending ac1 -> ac2 realtime advertisement and additional message")
ac1_webxdc_msg.send_webxdc_realtime_advertisement()
log("sending ac2 -> ac1 realtime advertisement and additional message")
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
return ac1_webxdc_msg, ac2_webxdc_msg
def setup_thread_send_realtime_data(msg, data):
def thread_run():
for _i in range(10):
msg.send_webxdc_realtime_data(data)
time.sleep(1)
threading.Thread(target=thread_run, daemon=True).start()
def wait_receive_realtime_data(msg_data_list):
account = msg_data_list[0][0].account
msg_data_list = msg_data_list[:]
log(f"account {account.id}: waiting for realtime data {msg_data_list}")
while msg_data_list:
event = account.wait_for_event()
if event.kind == EventType.WEBXDC_REALTIME_DATA:
for i, (msg, data) in enumerate(msg_data_list):
if msg.id == event.msg_id:
assert data == event.data
log(f"msg {msg.id}: got correct realtime data {data}")
del msg_data_list[i]
break
def test_realtime_sequentially(acfactory, path_to_webxdc):
"""Test two peers trying to establish connection sequentially."""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1.create_chat(ac2)
ac2.create_chat(ac1)
# share a webxdc app between ac1 and ac2
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
snapshot = ac2_webxdc_msg.get_snapshot()
assert snapshot.text == "play"
# send iroh announcements sequentially
log("sending ac1 -> ac2 realtime advertisement and additional message")
ac1_webxdc_msg.send_webxdc_realtime_advertisement()
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
log("waiting for incoming message on ac2")
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping1"
log("sending ac2 -> ac1 realtime advertisement and additional message")
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
log("waiting for incoming message on ac1")
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping2"
log("sending realtime data ac1 -> ac2")
ac1_webxdc_msg.send_webxdc_realtime_data(b"foo")
log("ac2: waiting for realtime data")
while 1:
event = ac2.wait_for_event()
if event.kind == EventType.WEBXDC_REALTIME_DATA:
assert event.data == list(b"foo")
break
def test_realtime_simultaneously(acfactory, path_to_webxdc):
"""Test two peers trying to establish connection simultaneously."""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
setup_thread_send_realtime_data(ac1_webxdc_msg, [10])
wait_receive_realtime_data([(ac2_webxdc_msg, [10])])
def test_two_parallel_realtime_simultaneously(acfactory, path_to_webxdc):
"""Test two peers trying to establish connection simultaneously."""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
ac1_webxdc_msg2, ac2_webxdc_msg2 = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
setup_thread_send_realtime_data(ac1_webxdc_msg, [10])
setup_thread_send_realtime_data(ac1_webxdc_msg2, [20])
setup_thread_send_realtime_data(ac2_webxdc_msg, [30])
setup_thread_send_realtime_data(ac2_webxdc_msg2, [40])
wait_receive_realtime_data([(ac1_webxdc_msg, [30]), (ac1_webxdc_msg2, [40])])
wait_receive_realtime_data([(ac2_webxdc_msg, [10]), (ac2_webxdc_msg2, [20])])
def test_no_duplicate_messages(acfactory, path_to_webxdc):
"""Test that messages are received only once."""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1_ac2_chat = ac1.create_chat(ac2)
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="webxdc", file=path_to_webxdc)
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
ac2_webxdc_msg.get_snapshot().chat.accept()
assert ac2_webxdc_msg.get_snapshot().text == "webxdc"
# Issue a "send" call in parallel with sending advertisement.
# Previously due to a bug this caused subscribing to the channel twice.
ac2_webxdc_msg.send_webxdc_realtime_data.future(b"foobar")
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
def thread_run():
for i in range(10):
data = str(i).encode()
ac1_webxdc_msg.send_webxdc_realtime_data(data)
time.sleep(1)
threading.Thread(target=thread_run, daemon=True).start()
while 1:
event = ac2.wait_for_event()
if event.kind == EventType.WEBXDC_REALTIME_DATA:
n = int(bytes(event.data).decode())
break
while 1:
event = ac2.wait_for_event()
if event.kind == EventType.WEBXDC_REALTIME_DATA:
assert int(bytes(event.data).decode()) > n
break

View File

@@ -0,0 +1,15 @@
def test_vcard(acfactory) -> None:
alice, bob = acfactory.get_online_accounts(2)
bob_addr = bob.get_config("addr")
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
alice_contact_charlie = alice.create_contact("charlie@example.org", "Charlie")
alice_chat_bob = alice_contact_bob.create_chat()
alice_chat_bob.send_contact(alice_contact_charlie)
event = bob.wait_for_incoming_msg_event()
message = bob.get_message_by_id(event.msg_id)
snapshot = message.get_snapshot()
assert snapshot.vcard_contact
assert snapshot.vcard_contact.addr == "charlie@example.org"

View File

@@ -1,123 +0,0 @@
#!/usr/bin/env python3
"""
Testing webxdc iroh connectivity
If you want to debug iroh at rust-trace/log level set
RUST_LOG=iroh_net=trace,iroh_gossip=trace
"""
import pytest
import time
import os
import sys
import logging
import random
import itertools
import sys
from deltachat_rpc_client import DeltaChat, EventType, SpecialContactId
@pytest.fixture()
def path_to_webxdc():
return "../test-data/webxdc/chess.xdc"
def test_realtime_sequentially(acfactory, path_to_webxdc):
"""Test two peers trying to establish connection sequentially."""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1.create_chat(ac2)
ac2.create_chat(ac1)
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping0")
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping0"
def log(msg):
print()
print("*" * 80 + "\n" + msg + "\n", file=sys.stderr)
print()
# share a webxdc app between ac1 and ac2
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
snapshot = ac2_webxdc_msg.get_snapshot()
assert snapshot.text == "play"
# send iroh announcements sequentially
log("sending ac1 -> ac2 realtime advertisement and additional message")
ac1._rpc.send_webxdc_realtime_advertisement(ac1.id, ac1_webxdc_msg.id)
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
log("waiting for incoming message on ac2")
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping1"
log("sending ac2 -> ac1 realtime advertisement and additional message")
ac2._rpc.send_webxdc_realtime_advertisement(ac2.id, ac2_webxdc_msg.id)
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
log("waiting for incoming message on ac1")
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping2"
log("sending realtime data ac1 -> ac2")
ac1._rpc.send_webxdc_realtime_data(ac1.id, ac1_webxdc_msg.id, [13, 15, 17])
log("ac2: waiting for realtime data")
while 1:
event = ac2.wait_for_event()
if event.kind == EventType.WEBXDC_REALTIME_DATA:
assert event.data == [13, 15, 17]
break
def test_realtime_simultaneously(acfactory, path_to_webxdc):
"""Test two peers trying to establish connection simultaneously."""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1.create_chat(ac2)
ac2.create_chat(ac1)
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping0")
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping0"
def log(msg):
print()
print("*" * 80 + "\n" + msg + "\n", file=sys.stderr)
print()
# share a webxdc app between ac1 and ac2
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
snapshot = ac2_webxdc_msg.get_snapshot()
assert snapshot.text == "play"
# send iroh announcements simultaneously
log("sending ac1 -> ac2 realtime advertisement and additional message")
ac1._rpc.send_webxdc_realtime_advertisement(ac1.id, ac1_webxdc_msg.id)
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
log("sending ac2 -> ac1 realtime advertisement and additional message")
ac2._rpc.send_webxdc_realtime_advertisement(ac2.id, ac2_webxdc_msg.id)
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
# Ensure that advertisements have been received.
log("waiting for incoming message on ac2")
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping1"
log("waiting for incoming message on ac1")
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
assert snapshot.text == "ping2"
log("sending realtime data ac1 -> ac2")
ac1._rpc.send_webxdc_realtime_data(ac1.id, ac1_webxdc_msg.id, [13, 15, 17])
log("ac2: waiting for realtime data")
while 1:
event = ac2.wait_for_event()
if event.kind == EventType.WEBXDC_REALTIME_DATA:
assert event.data == [13, 15, 17]
break

View File

@@ -28,5 +28,5 @@ commands =
[pytest]
timeout = 300
log_cli = true
#log_cli = true
log_level = debug

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.138.5"
version = "1.139.5"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -14,15 +14,14 @@ deltachat-jsonrpc = { path = "../deltachat-jsonrpc", default-features = false }
deltachat = { path = "..", default-features = false }
anyhow = "1"
env_logger = { version = "0.11.3" }
futures-lite = "2.3.0"
log = "0.4"
serde_json = "1"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.37.0", features = ["io-std"] }
tokio-util = "0.7.9"
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
[features]
default = ["vendored"]

View File

@@ -20,7 +20,9 @@ import { C } from "@deltachat/jsonrpc-client";
async function main() {
const dc = await startDeltaChat("deltachat-data");
console.log(await dc.rpc.getSystemInfo());
dc.close()
}
main()
```
For a more complete example refer to https://github.com/deltachat-bot/echo/pull/69/files (TODO change link when pr is merged).
@@ -44,12 +46,11 @@ references:
When you import this package it searches for the rpc server in the following locations and order:
1. `DELTA_CHAT_RPC_SERVER` environment variable
2. in PATH
- unless `DELTA_CHAT_SKIP_PATH=1` is specified
- searches in .cargo/bin directory first
- but there an additional version check is performed
2. use the PATH when `{takeVersionFromPATH: true}` is supplied in the options.
3. prebuilds in npm packages
so by default it uses the prebuilds.
## How do you built this package in CI
- To build platform packages, run the `build_platform_package.py` script:

View File

@@ -1,8 +1,8 @@
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
export interface SearchOptions {
/** whether to disable looking for deltachat-rpc-server inside of $PATH */
skipSearchInPath: boolean;
/** whether take deltachat-rpc-server inside of $PATH*/
takeVersionFromPATH: boolean;
/** whether to disable the DELTA_CHAT_RPC_SERVER environment variable */
disableEnvPath: boolean;

View File

@@ -1,15 +1,9 @@
//@ts-check
import { execFile, spawn } from "node:child_process";
import { stat, readdir } from "node:fs/promises";
import { spawn } from "node:child_process";
import { stat } from "node:fs/promises";
import os from "node:os";
import { join, basename } from "node:path";
import process from "node:process";
import { promisify } from "node:util";
import {
ENV_VAR_NAME,
PATH_EXECUTABLE_NAME,
SKIP_SEARCH_IN_PATH,
} from "./src/const.js";
import { ENV_VAR_NAME, PATH_EXECUTABLE_NAME } from "./src/const.js";
import {
ENV_VAR_LOCATION_NOT_FOUND,
FAILED_TO_START_SERVER_EXECUTABLE,
@@ -39,38 +33,13 @@ function findRPCServerInNodeModules() {
}
}
/**
* @returns {Promise<string>}
*/
async function getLocationInPath() {
const exec = promisify(execFile);
if (os.platform() === "win32") {
const { stdout: executable } = await exec("where", [PATH_EXECUTABLE_NAME], {
shell: true,
});
return executable;
}
try {
const { stdout: executable } = await exec(
"command",
["-v", PATH_EXECUTABLE_NAME],
{ shell: true }
);
return executable;
} catch (error) {
if (error.code > 0) return "";
else throw error;
}
}
/** @type {import("./index").FnTypes.getRPCServerPath} */
export async function getRPCServerPath(
options = { skipSearchInPath: false, disableEnvPath: false }
) {
// @TODO: improve confusing naming of these options
const { skipSearchInPath, disableEnvPath } = options;
export async function getRPCServerPath(options = {}) {
const { takeVersionFromPATH, disableEnvPath } = {
takeVersionFromPATH: false,
disableEnvPath: false,
...options,
};
// 1. check if it is set as env var
if (process.env[ENV_VAR_NAME] && !disableEnvPath) {
try {
@@ -85,35 +54,9 @@ export async function getRPCServerPath(
return process.env[ENV_VAR_NAME];
}
// 2. check if it can be found in PATH
if (!process.env[SKIP_SEARCH_IN_PATH] && !skipSearchInPath) {
const executable = await getLocationInPath();
// by just trying to execute it and then use "command -v deltachat-rpc-server" (unix) or "where deltachat-rpc-server" (windows) to get the path to the executable
if (executable.length > 1) {
// test if it is the right version
try {
// for some unknown reason it is in stderr and not in stdout
const { stderr } = await promisify(execFile)(
executable,
["--version"],
{ shell: true }
);
const version = stderr.slice(0, stderr.indexOf("\n"));
if (package_json.version !== version) {
throw new Error(
`version mismatch: (npm package: ${package_json.version}) (installed ${PATH_EXECUTABLE_NAME} version: ${version})`
);
} else {
return executable;
}
} catch (error) {
console.error(
"Found executable in PATH, but there was an error: " + error
);
console.error("So falling back to using prebuild...");
}
}
// 2. check if PATH should be used
if (takeVersionFromPATH) {
return PATH_EXECUTABLE_NAME;
}
// 3. check for prebuilds
@@ -123,11 +66,11 @@ export async function getRPCServerPath(
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
/** @type {import("./index").FnTypes.startDeltaChat} */
export async function startDeltaChat(directory, options) {
export async function startDeltaChat(directory, options = {}) {
const pathToServerBinary = await getRPCServerPath(options);
const server = spawn(pathToServerBinary, {
env: {
RUST_LOG: process.env.RUST_LOG || "info",
RUST_LOG: process.env.RUST_LOG,
DC_ACCOUNTS_PATH: directory,
},
stdio: ["pipe", "pipe", options.muteStdErr ? "ignore" : "inherit"],

View File

@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "1.138.5"
"version": "1.139.5"
}

View File

@@ -2,5 +2,4 @@
export const PATH_EXECUTABLE_NAME = 'deltachat-rpc-server'
export const ENV_VAR_NAME = "DELTA_CHAT_RPC_SERVER"
export const SKIP_SEARCH_IN_PATH = "DELTA_CHAT_SKIP_PATH"
export const ENV_VAR_NAME = "DELTA_CHAT_RPC_SERVER"

View File

@@ -11,7 +11,7 @@ use deltachat::constants::DC_VERSION_STR;
use deltachat_jsonrpc::api::{Accounts, CommandApi};
use futures_lite::stream::StreamExt;
use tokio::io::{self, AsyncBufReadExt, BufReader};
use tracing_subscriber::{prelude::*, EnvFilter};
use tracing_subscriber::EnvFilter;
use yerpc::RpcServer as _;
#[cfg(target_family = "unix")]
@@ -29,6 +29,9 @@ async fn main() {
// "For technical reasons, stdin is implemented by using an ordinary blocking read on a separate
// thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang
// until the user presses enter."
if let Err(error) = &r {
log::error!("Fatal error: {error:#}.")
}
std::process::exit(if r.is_ok() { 0 } else { 1 });
}
@@ -61,13 +64,13 @@ async fn main_impl() -> Result<()> {
#[cfg(target_family = "unix")]
let mut sigterm = signal_unix::signal(signal_unix::SignalKind::terminate())?;
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
.with(EnvFilter::builder().from_env_lossy())
.try_init()
.ok();
// Logs from `log` crate and traces from `tracing` crate
// are configurable with `RUST_LOG` environment variable
// and go to stderr to avoid interferring with JSON-RPC using stdout.
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.init();
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
log::info!("Starting with accounts directory `{}`.", path);

View File

@@ -45,7 +45,6 @@ skip = [
{ name = "dlopen2", version = "0.4.1" },
{ name = "ed25519-dalek", version = "1.0.1" },
{ name = "ed25519", version = "1.5.3" },
{ name = "env_logger", version = "0.10.2" },
{ name = "event-listener", version = "2.5.3" },
{ name = "event-listener", version = "4.0.3" },
{ name = "fastrand", version = "1.9.0" },
@@ -53,6 +52,7 @@ skip = [
{ name = "getrandom", version = "<0.2" },
{ name = "http-body", version = "0.4.6" },
{ name = "http", version = "0.2.12" },
{ name = "hyper-rustls", version = "0.24.2" },
{ name = "hyper", version = "0.14.28" },
{ name = "idna", version = "0.4.0" },
{ name = "netlink-packet-core", version = "0.5.0" },
@@ -62,8 +62,6 @@ skip = [
{ name = "pem-rfc7468", version = "0.6.0" },
{ name = "pem", version = "1.1.1" },
{ name = "pkcs8", version = "0.9.0" },
{ name = "proc-macro-error-attr", version = "0.4.12" },
{ name = "proc-macro-error", version = "0.4.12" },
{ name = "quick-error", version = "<2.0" },
{ name = "rand_chacha", version = "<0.3" },
{ name = "rand_core", version = "<0.6" },
@@ -72,7 +70,11 @@ skip = [
{ name = "redox_syscall", version = "0.3.5" },
{ name = "regex-automata", version = "0.1.10" },
{ name = "regex-syntax", version = "0.6.29" },
{ name = "reqwest", version = "0.11.27" },
{ name = "ring", version = "0.16.20" },
{ name = "rustls-pemfile", version = "1.0.4" },
{ name = "rustls", version = "0.21.11" },
{ name = "rustls-webpki", version = "0.101.7" },
{ name = "sec1", version = "0.3.0" },
{ name = "sha2", version = "<0.10" },
{ name = "signature", version = "1.6.4" },
@@ -86,9 +88,11 @@ skip = [
{ name = "system-configuration-sys", version = "0.5.0" },
{ name = "system-configuration", version = "0.5.1" },
{ name = "time", version = "<0.3" },
{ name = "tokio-rustls", version = "0.24.1" },
{ name = "toml_edit", version = "0.21.1" },
{ name = "untrusted", version = "0.7.1" },
{ name = "wasi", version = "<0.11" },
{ name = "webpki-roots", version ="0.25.4" },
{ name = "windows_aarch64_gnullvm", version = "<0.52" },
{ name = "windows_aarch64_msvc", version = "<0.52" },
{ name = "windows-core", version = "<0.54.0" },
@@ -102,6 +106,7 @@ skip = [
{ 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" },
{ name = "x509-parser", version = "<0.16.0" },
]
@@ -134,4 +139,5 @@ license-files = [
github = [
"async-email",
"deltachat",
"n0-computer",
]

View File

@@ -542,6 +542,7 @@
])
cargo-deny
rust-analyzer-nightly
cargo-nextest
perl # needed to build vendored OpenSSL
git-cliff
];

View File

@@ -30,6 +30,7 @@ module.exports = {
DC_DOWNLOAD_IN_PROGRESS: 1000,
DC_DOWNLOAD_UNDECIPHERABLE: 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
DC_EVENT_CHANNEL_OVERFLOW: 2400,
DC_EVENT_CHATLIST_CHANGED: 2300,
DC_EVENT_CHATLIST_ITEM_CHANGED: 2301,
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: 2021,
@@ -66,6 +67,7 @@ module.exports = {
DC_EVENT_SMTP_MESSAGE_SENT: 103,
DC_EVENT_WARNING: 300,
DC_EVENT_WEBXDC_INSTANCE_DELETED: 2121,
DC_EVENT_WEBXDC_REALTIME_DATA: 2150,
DC_EVENT_WEBXDC_STATUS_UPDATE: 2120,
DC_GCL_ADD_ALLDONE_HINT: 4,
DC_GCL_ADD_SELF: 2,

View File

@@ -37,7 +37,9 @@ module.exports = {
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2150: 'DC_EVENT_WEBXDC_REALTIME_DATA',
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
2300: 'DC_EVENT_CHATLIST_CHANGED',
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED'
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
2400: 'DC_EVENT_CHANNEL_OVERFLOW'
}

View File

@@ -30,6 +30,7 @@ export enum C {
DC_DOWNLOAD_IN_PROGRESS = 1000,
DC_DOWNLOAD_UNDECIPHERABLE = 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200,
DC_EVENT_CHANNEL_OVERFLOW = 2400,
DC_EVENT_CHATLIST_CHANGED = 2300,
DC_EVENT_CHATLIST_ITEM_CHANGED = 2301,
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021,
@@ -66,6 +67,7 @@ export enum C {
DC_EVENT_SMTP_MESSAGE_SENT = 103,
DC_EVENT_WARNING = 300,
DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121,
DC_EVENT_WEBXDC_REALTIME_DATA = 2150,
DC_EVENT_WEBXDC_STATUS_UPDATE = 2120,
DC_GCL_ADD_ALLDONE_HINT = 4,
DC_GCL_ADD_SELF = 2,
@@ -338,7 +340,9 @@ export const EventId2EventName: { [key: number]: string } = {
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2150: 'DC_EVENT_WEBXDC_REALTIME_DATA',
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
2300: 'DC_EVENT_CHATLIST_CHANGED',
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
2400: 'DC_EVENT_CHANNEL_OVERFLOW',
}

View File

@@ -55,5 +55,5 @@
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.138.5"
"version": "1.139.5"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "1.138.5"
version = "1.139.5"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"
requires-python = ">=3.7"

View File

@@ -1 +1 @@
2024-05-16
2024-05-23

View File

@@ -485,10 +485,6 @@ impl Config {
/// Read a configuration from the given file into memory.
pub async fn from_file(file: PathBuf, writable: bool) -> Result<Self> {
let dir = file
.parent()
.context("Cannot get config file directory")?
.to_path_buf();
let mut config = Self::new_nosync(file, writable).await?;
let bytes = fs::read(&config.file)
.await
@@ -500,9 +496,13 @@ impl Config {
// Convert them to relative paths.
let mut modified = false;
for account in &mut config.inner.accounts {
if let Ok(new_dir) = account.dir.strip_prefix(&dir) {
account.dir = new_dir.to_path_buf();
modified = true;
if account.dir.is_absolute() {
if let Some(old_path_parent) = account.dir.parent() {
if let Ok(new_path) = account.dir.strip_prefix(old_path_parent) {
account.dir = new_path.to_path_buf();
modified = true;
}
}
}
}
if modified && writable {

View File

@@ -1191,6 +1191,21 @@ mod tests {
.await
.unwrap();
send_image_check_mediaquality(
Viewtype::File,
Some("1"),
bytes,
"png",
false, // no Exif
1920,
1080,
0,
1920,
1080,
)
.await
.unwrap();
// This will be sent as Image, see [`BlobObject::maybe_sticker`] for explanation.
send_image_check_mediaquality(
Viewtype::Sticker,
@@ -1321,6 +1336,11 @@ mod tests {
.get_blobdir()
.join("saved-".to_string() + &bob_msg.get_filename().unwrap());
bob_msg.save_file(&bob, &file_saved).await?;
if viewtype == Viewtype::File {
assert_eq!(file_saved.extension().unwrap(), extension);
let bytes1 = fs::read(&file_saved).await?;
assert_eq!(&bytes1, bytes);
}
let blob = BlobObject::new_from_path(&bob, &file_saved).await?;
let (_, exif) = blob.metadata()?;

View File

@@ -292,7 +292,7 @@ impl ChatId {
ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
.await
.map(|chat| chat.id)?;
Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat).await?;
ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
chat_id
} else {
warn!(
@@ -489,7 +489,7 @@ impl ChatId {
// went to "contact requests" list rather than normal chatlist.
for contact_id in get_chat_contacts(context, self).await? {
if contact_id != ContactId::SELF {
Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat)
ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
.await?;
}
}
@@ -2619,6 +2619,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
.get_blob(Param::File, context, !msg.is_increation())
.await?
.with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
let send_as_is = msg.viewtype == Viewtype::File;
if msg.viewtype == Viewtype::File || msg.viewtype == Viewtype::Image {
// Correct the type, take care not to correct already very special
@@ -2645,8 +2646,9 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
}
let mut maybe_sticker = msg.viewtype == Viewtype::Sticker;
if msg.viewtype == Viewtype::Image
|| maybe_sticker && !msg.param.exists(Param::ForceSticker)
if !send_as_is
&& (msg.viewtype == Viewtype::Image
|| maybe_sticker && !msg.param.exists(Param::ForceSticker))
{
blob.recode_to_image_size(context, &mut maybe_sticker)
.await?;

View File

@@ -1,6 +1,6 @@
//! Contacts module
use std::cmp::Reverse;
use std::cmp::{min, Reverse};
use std::collections::BinaryHeap;
use std::fmt;
use std::path::{Path, PathBuf};
@@ -11,8 +11,8 @@ use async_channel::{self as channel, Receiver, Sender};
use base64::Engine as _;
pub use deltachat_contact_tools::may_be_valid_addr;
use deltachat_contact_tools::{
self as contact_tools, addr_cmp, addr_normalize, normalize_name, sanitize_name_and_addr,
strip_rtlo_characters, ContactAddress, VcardContact,
self as contact_tools, addr_cmp, addr_normalize, sanitize_name_and_addr, strip_rtlo_characters,
ContactAddress, VcardContact,
};
use deltachat_derive::{FromSql, ToSql};
use rusqlite::OptionalExtension;
@@ -20,14 +20,15 @@ use serde::{Deserialize, Serialize};
use tokio::task;
use tokio::time::{timeout, Duration};
use crate::aheader::EncryptPreference;
use crate::aheader::{Aheader, EncryptPreference};
use crate::blob::BlobObject;
use crate::chat::{ChatId, ChatIdBlocked, ProtectionStatus};
use crate::color::str_to_color;
use crate::config::Config;
use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{load_self_public_key, DcKey};
use crate::key::{load_self_public_key, DcKey, SignedPublicKey};
use crate::log::LogExt;
use crate::login_param::LoginParam;
use crate::message::MessageState;
@@ -36,7 +37,9 @@ use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::sql::{self, params_iter};
use crate::sync::{self, Sync::*};
use crate::tools::{duration_to_str, get_abs_path, improve_single_line_input, time, SystemTime};
use crate::tools::{
duration_to_str, get_abs_path, improve_single_line_input, smeared_time, time, SystemTime,
};
use crate::{chat, chatlist_events, stock_str};
/// Time during which a contact is considered as seen recently.
@@ -120,6 +123,29 @@ impl ContactId {
.await?;
Ok(())
}
/// Updates the origin of the contacts, but only if `origin` is higher than the current one.
pub(crate) async fn scaleup_origin(
context: &Context,
ids: &[Self],
origin: Origin,
) -> Result<()> {
context
.sql
.execute(
&format!(
"UPDATE contacts SET origin=? WHERE id IN ({}) AND origin<?",
sql::repeat_vars(ids.len())
),
rusqlite::params_from_iter(
params_iter(&[origin])
.chain(params_iter(ids))
.chain(params_iter(&[origin])),
),
)
.await?;
Ok(())
}
}
impl fmt::Display for ContactId {
@@ -166,9 +192,13 @@ pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<Str
let mut vcard_contacts = Vec::with_capacity(contacts.len());
for id in contacts {
let c = Contact::get_by_id(context, *id).await?;
let key = Peerstate::from_addr(context, &c.addr)
.await?
.and_then(|peerstate| peerstate.peek_key(false).map(|k| k.to_base64()));
let key = match *id {
ContactId::SELF => Some(load_self_public_key(context).await?),
_ => Peerstate::from_addr(context, &c.addr)
.await?
.and_then(|peerstate| peerstate.take_key(false)),
};
let key = key.map(|k| k.to_base64());
let profile_image = match c.get_profile_image(context).await? {
None => None,
Some(path) => tokio::fs::read(path)
@@ -189,6 +219,129 @@ pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<Str
Ok(contact_tools::make_vcard(&vcard_contacts))
}
/// Imports contacts from the given vCard.
///
/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
/// regardless of whether they are just created, modified or left untouched.
pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
let contacts = contact_tools::parse_vcard(vcard);
let mut contact_ids = Vec::with_capacity(contacts.len());
for c in &contacts {
let Ok(id) = import_vcard_contact(context, c)
.await
.with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
.log_err(context)
else {
continue;
};
contact_ids.push(id);
}
Ok(contact_ids)
}
async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
// Importing a vCard is also an explicit user action like creating a chat with the contact. We
// mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
// want `contact.authname` to be saved as the authname and not a locally given name.
let origin = Origin::CreateChat;
let (id, modified) =
match Contact::add_or_lookup(context, &contact.authname, &addr, origin).await {
Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
Ok(val) => val,
};
if modified != Modifier::None {
context.emit_event(EventType::ContactsChanged(Some(id)));
}
let key = contact.key.as_ref().and_then(|k| {
SignedPublicKey::from_base64(k)
.with_context(|| {
format!(
"import_vcard_contact: Cannot decode key for {}",
contact.addr
)
})
.log_err(context)
.ok()
});
if let Some(public_key) = key {
let timestamp = contact
.timestamp
.as_ref()
.map_or(0, |&t| min(t, smeared_time(context)));
let aheader = Aheader {
addr: contact.addr.clone(),
public_key,
prefer_encrypt: EncryptPreference::Mutual,
};
let peerstate = match Peerstate::from_addr(context, &aheader.addr).await {
Err(e) => {
warn!(
context,
"import_vcard_contact: Cannot create peerstate from {}: {e:#}.", contact.addr
);
return Ok(id);
}
Ok(p) => p,
};
let peerstate = if let Some(mut p) = peerstate {
p.apply_gossip(&aheader, timestamp);
p
} else {
Peerstate::from_gossip(&aheader, timestamp)
};
if let Err(e) = peerstate.save_to_db(&context.sql).await {
warn!(
context,
"import_vcard_contact: Could not save peerstate for {}: {e:#}.", contact.addr
);
return Ok(id);
}
if let Err(e) = peerstate
.handle_fingerprint_change(context, timestamp)
.await
{
warn!(
context,
"import_vcard_contact: handle_fingerprint_change() failed for {}: {e:#}.",
contact.addr
);
return Ok(id);
}
}
if modified != Modifier::Created {
return Ok(id);
}
let path = match &contact.profile_image {
Some(image) => match BlobObject::store_from_base64(context, image, "avatar").await {
Err(e) => {
warn!(
context,
"import_vcard_contact: Could not decode and save avatar for {}: {e:#}.",
contact.addr
);
None
}
Ok(path) => Some(path),
},
None => None,
};
if let Some(path) = path {
// Currently this value doesn't matter as we don't import the contact of self.
let was_encrypted = false;
if let Err(e) =
set_profile_image(context, id, &AvatarAction::Change(path), was_encrypted).await
{
warn!(
context,
"import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
);
}
}
Ok(id)
}
/// An object representing a single contact in memory.
///
/// The contact object is not updated.
@@ -394,6 +547,10 @@ impl Contact {
{
if contact_id == ContactId::SELF {
contact.name = stock_str::self_msg(context).await;
contact.authname = context
.get_config(Config::Displayname)
.await?
.unwrap_or_default();
contact.addr = context
.get_config(Config::ConfiguredAddr)
.await?
@@ -808,7 +965,6 @@ impl Contact {
for (name, addr) in split_address_book(addr_book) {
let (name, addr) = sanitize_name_and_addr(name, addr);
let name = normalize_name(&name);
match ContactAddress::new(&addr) {
Ok(addr) => {
match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
@@ -1383,22 +1539,6 @@ impl Contact {
.await?;
Ok(exists)
}
/// Updates the origin of the contact, but only if new origin is higher than the current one.
pub async fn scaleup_origin_by_id(
context: &Context,
contact_id: ContactId,
origin: Origin,
) -> Result<()> {
context
.sql
.execute(
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
(origin, contact_id, origin),
)
.await?;
Ok(())
}
}
pub(crate) async fn set_blocked(
@@ -1784,7 +1924,7 @@ impl RecentlySeenLoop {
#[cfg(test)]
mod tests {
use deltachat_contact_tools::may_be_valid_addr;
use deltachat_contact_tools::{may_be_valid_addr, normalize_name};
use super::*;
use crate::chat::{get_chat_contacts, send_text_msg, Chat};
@@ -2849,7 +2989,7 @@ Until the false-positive is fixed:
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_make_vcard() -> Result<()> {
async fn test_make_n_import_vcard() -> Result<()> {
let alice = &TestContext::new_alice().await;
let bob = &TestContext::new_bob().await;
bob.set_config(Config::Displayname, Some("Bob")).await?;
@@ -2883,8 +3023,8 @@ Until the false-positive is fixed:
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0].addr, bob_addr);
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(contacts[0].key, Some(key_base64));
assert_eq!(contacts[0].profile_image, Some(avatar_base64));
assert_eq!(*contacts[0].key.as_ref().unwrap(), key_base64);
assert_eq!(*contacts[0].profile_image.as_ref().unwrap(), avatar_base64);
let timestamp = *contacts[0].timestamp.as_ref().unwrap();
assert!(t0 <= timestamp && timestamp <= t1);
assert_eq!(contacts[1].addr, "fiona@example.net".to_string());
@@ -2894,6 +3034,110 @@ Until the false-positive is fixed:
let timestamp = *contacts[1].timestamp.as_ref().unwrap();
assert!(t0 <= timestamp && timestamp <= t1);
let alice = &TestContext::new_alice().await;
alice.evtracker.clear_events();
let contact_ids = import_vcard(alice, &vcard).await?;
assert_eq!(contact_ids.len(), 2);
for _ in 0..contact_ids.len() {
alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged(Some(_))))
.await;
}
let vcard = make_vcard(alice, &[contact_ids[0], contact_ids[1]]).await?;
// This should be the same vCard except timestamps, check that roughly.
let contacts = contact_tools::parse_vcard(&vcard);
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0].addr, bob_addr);
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(*contacts[0].key.as_ref().unwrap(), key_base64);
assert_eq!(*contacts[0].profile_image.as_ref().unwrap(), avatar_base64);
assert!(contacts[0].timestamp.is_ok());
assert_eq!(contacts[1].addr, "fiona@example.net".to_string());
let chat_id = ChatId::create_for_contact(alice, contact_ids[0]).await?;
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
// Bob only actually imports Fiona, though `ContactId::SELF` is also returned.
bob.evtracker.clear_events();
let contact_ids = import_vcard(bob, &vcard).await?;
bob.emit_event(EventType::Test);
assert_eq!(contact_ids.len(), 2);
assert_eq!(contact_ids[0], ContactId::SELF);
let ev = bob
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. }))
.await;
assert_eq!(ev, EventType::ContactsChanged(Some(contact_ids[1])));
let ev = bob
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. } | EventType::Test))
.await;
assert_eq!(ev, EventType::Test);
let vcard = make_vcard(bob, &[contact_ids[1]]).await?;
let contacts = contact_tools::parse_vcard(&vcard);
assert_eq!(contacts.len(), 1);
assert_eq!(contacts[0].addr, "fiona@example.net");
assert_eq!(contacts[0].authname, "".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image, None);
assert!(contacts[0].timestamp.is_ok());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_import_vcard_updates_only_key() -> Result<()> {
let alice = &TestContext::new_alice().await;
let bob = &TestContext::new_bob().await;
let bob_addr = &bob.get_config(Config::Addr).await?.unwrap();
bob.set_config(Config::Displayname, Some("Bob")).await?;
let vcard = make_vcard(bob, &[ContactId::SELF]).await?;
alice.evtracker.clear_events();
let alice_bob_id = import_vcard(alice, &vcard).await?[0];
let ev = alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. }))
.await;
assert_eq!(ev, EventType::ContactsChanged(Some(alice_bob_id)));
let chat_id = ChatId::create_for_contact(alice, alice_bob_id).await?;
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
let bob = &TestContext::new().await;
bob.configure_addr(bob_addr).await;
bob.set_config(Config::Displayname, Some("Not Bob")).await?;
let avatar_path = bob.dir.path().join("avatar.png");
let avatar_bytes = include_bytes!("../test-data/image/avatar64x64.png");
tokio::fs::write(&avatar_path, avatar_bytes).await?;
bob.set_config(Config::Selfavatar, Some(avatar_path.to_str().unwrap()))
.await?;
SystemTime::shift(Duration::from_secs(1));
let vcard1 = make_vcard(bob, &[ContactId::SELF]).await?;
assert_eq!(import_vcard(alice, &vcard1).await?, vec![alice_bob_id]);
let alice_bob_contact = Contact::get_by_id(alice, alice_bob_id).await?;
assert_eq!(alice_bob_contact.get_authname(), "Bob");
assert_eq!(alice_bob_contact.get_profile_image(alice).await?, None);
let msg = alice.get_last_msg_in(chat_id).await;
assert!(msg.is_info());
assert_eq!(
msg.get_text(),
stock_str::contact_setup_changed(alice, bob_addr).await
);
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
// The old vCard is imported, but doesn't change Bob's key for Alice.
import_vcard(alice, &vcard).await?.first().unwrap();
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
Ok(())
}
}

View File

@@ -339,6 +339,7 @@ impl Context {
) -> Result<Context> {
let context =
Self::new_closed(dbfile, id, events, stock_strings, Default::default()).await?;
// Open the database if is not encrypted.
if context.check_passphrase("".to_string()).await? {
context.sql.open(&context, "".to_string()).await?;
@@ -1375,43 +1376,6 @@ pub fn get_version_str() -> &'static str {
&DC_VERSION_STR
}
#[derive(Default, Debug)]
struct CollectVisitor(HashMap<String, String>);
impl tracing::field::Visit for CollectVisitor {
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
self.0.insert(field.to_string(), value.to_string());
}
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
self.0.insert(field.to_string(), value.to_string());
}
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
self.0.insert(field.to_string(), value.to_string());
}
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
self.0.insert(field.to_string(), value.to_string());
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.0.insert(field.to_string(), value.to_string());
}
fn record_error(
&mut self,
field: &tracing::field::Field,
value: &(dyn std::error::Error + 'static),
) {
self.0.insert(field.to_string(), value.to_string());
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.0.insert(field.to_string(), format!("{:?}", value));
}
}
#[cfg(test)]
mod tests {
use anyhow::Context as _;

View File

@@ -71,7 +71,14 @@ impl EventEmitter {
/// [`try_recv`]: Self::try_recv
pub async fn recv(&self) -> Option<Event> {
let mut lock = self.0.lock().await;
lock.recv().await.ok()
match lock.recv().await {
Err(async_broadcast::RecvError::Overflowed(n)) => Some(Event {
id: 0,
typ: EventType::EventChannelOverflow { n },
}),
Err(async_broadcast::RecvError::Closed) => None,
Ok(event) => Some(event),
}
}
/// Tries to receive an event without blocking.
@@ -86,8 +93,19 @@ impl EventEmitter {
// to avoid blocking
// in case there is a concurrent call to `recv`.
let mut lock = self.0.try_lock()?;
let event = lock.try_recv()?;
Ok(event)
match lock.try_recv() {
Err(async_broadcast::TryRecvError::Overflowed(n)) => {
// Some events have been lost,
// but the channel is not closed.
Ok(Event {
id: 0,
typ: EventType::EventChannelOverflow { n },
})
}
res @ (Err(async_broadcast::TryRecvError::Empty)
| Err(async_broadcast::TryRecvError::Closed)
| Ok(_)) => Ok(res?),
}
}
}

View File

@@ -311,4 +311,14 @@ pub enum EventType {
/// ID of the changed chat
chat_id: Option<ChatId>,
},
/// Event for using in tests, e.g. as a fence between normally generated events.
#[cfg(test)]
Test,
/// Inform than some events have been skipped due to event channel overflow.
EventChannelOverflow {
/// Number of events skipped.
n: u64,
},
}

View File

@@ -1472,13 +1472,15 @@ impl Session {
admin = m.value;
}
"/shared/vendor/deltachat/irohrelay" => {
if let Some(url) = m.value.as_deref().and_then(|s| Url::parse(s).ok()) {
iroh_relay = Some(url);
} else {
warn!(
context,
"Got invalid URL from iroh relay metadata: {:?}.", m.value
);
if let Some(value) = m.value {
if let Ok(url) = Url::parse(&value) {
iroh_relay = Some(url);
} else {
warn!(
context,
"Got invalid URL from iroh relay metadata: {:?}.", value
);
}
}
}
_ => {}

View File

@@ -885,6 +885,7 @@ mod tests {
use super::*;
use crate::config::Config;
use crate::message::MessageState;
use crate::receive_imf::receive_imf;
use crate::test_utils::{TestContext, TestContextManager};
use crate::tools::SystemTime;
@@ -1015,6 +1016,54 @@ Content-Disposition: attachment; filename="location.kml"
Ok(())
}
/// Tests that `location.kml` is not hidden and not seen if it contains a message.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn receive_visible_location_kml() -> Result<()> {
let alice = TestContext::new_alice().await;
receive_imf(
&alice,
br#"Subject: locations
MIME-Version: 1.0
To: <alice@example.org>
From: <bob@example.net>
Date: Tue, 21 Dec 2021 00:00:00 +0000
Chat-Version: 1.0
Message-ID: <foobar@localhost>
Content-Type: multipart/mixed; boundary="U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF"
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Text message.
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF
Content-Type: application/vnd.google-earth.kml+xml
Content-Disposition: attachment; filename="location.kml"
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document addr="bob@example.net">
<Placemark><Timestamp><when>2021-11-21T00:00:00Z</when></Timestamp><Point><coordinates accuracy="1.0000000000000000">10.00000000000000,20.00000000000000</coordinates></Point></Placemark>
</Document>
</kml>
--U8BOG8qNXfB0GgLiQ3PKUjlvdIuLRF--"#,
false,
)
.await?;
let received_msg = alice.get_last_msg().await;
assert_eq!(received_msg.text, "Text message.");
assert_eq!(received_msg.state, MessageState::InFresh);
let locations = get_range(&alice, None, None, 0, 0).await?;
assert_eq!(locations.len(), 1);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_send_locations_to_chat() -> Result<()> {
let alice = TestContext::new_alice().await;

View File

@@ -2292,6 +2292,7 @@ mod tests {
async fn test_set_override_sender_name() {
// send message with overridden sender name
let alice = TestContext::new_alice().await;
let alice2 = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
let chat = alice.create_chat(&bob).await;
let contact_id = *chat::get_chat_contacts(&alice, chat.id)
@@ -2311,6 +2312,7 @@ mod tests {
assert_eq!(msg.get_sender_name(&contact), "over ride".to_string());
assert_ne!(contact.get_display_name(), "over ride".to_string());
chat::send_msg(&alice, chat.id, &mut msg).await.unwrap();
let sent_msg = alice.pop_sent_msg().await;
// bob receives that message
let chat = bob.create_chat(&alice).await;
@@ -2320,7 +2322,7 @@ mod tests {
.first()
.unwrap();
let contact = Contact::get_by_id(&bob, contact_id).await.unwrap();
let msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
let msg = bob.recv_msg(&sent_msg).await;
assert_eq!(msg.chat_id, chat.id);
assert_eq!(msg.text, "bla blubb");
assert_eq!(
@@ -2334,6 +2336,13 @@ mod tests {
// (mailing lists may also use `Sender:`-header)
let chat = Chat::load_from_db(&bob, msg.chat_id).await.unwrap();
assert_ne!(chat.typ, Chattype::Mailinglist);
// Alice receives message on another device.
let msg = alice2.recv_msg(&sent_msg).await;
assert_eq!(
msg.get_override_sender_name(),
Some("over ride".to_string())
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -13,7 +13,7 @@ use crate::blob::BlobObject;
use crate::chat::{self, Chat};
use crate::config::Config;
use crate::constants::{Chattype, DC_FROM_HANDSHAKE};
use crate::contact::Contact;
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
use crate::e2ee::EncryptHelper;
use crate::ephemeral::Timer as EphemeralTimer;
@@ -155,6 +155,7 @@ impl<'a> MimeFactory<'a> {
};
let mut recipients = Vec::with_capacity(5);
let mut recipient_ids = HashSet::new();
let mut req_mdn = false;
if chat.is_self_talk() {
@@ -169,7 +170,7 @@ impl<'a> MimeFactory<'a> {
context
.sql
.query_map(
"SELECT c.authname, c.addr \
"SELECT c.authname, c.addr, c.id \
FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \
WHERE cc.chat_id=? AND cc.contact_id>9;",
@@ -177,19 +178,23 @@ impl<'a> MimeFactory<'a> {
|row| {
let authname: String = row.get(0)?;
let addr: String = row.get(1)?;
Ok((authname, addr))
let id: ContactId = row.get(2)?;
Ok((authname, addr, id))
},
|rows| {
for row in rows {
let (authname, addr) = row?;
let (authname, addr, id) = row?;
if !recipients_contain_addr(&recipients, &addr) {
recipients.push((authname, addr));
}
recipient_ids.insert(id);
}
Ok(())
},
)
.await?;
let recipient_ids: Vec<_> = recipient_ids.into_iter().collect();
ContactId::scaleup_origin(context, &recipient_ids, Origin::OutgoingTo).await?;
if !msg.is_system_message()
&& msg.param.get_int(Param::Reaction).unwrap_or_default() == 0

View File

@@ -1587,9 +1587,8 @@ impl MimeMessage {
.get_header_value(HeaderDef::MessageId)
.and_then(|v| parse_message_id(&v).ok())
{
let mut to_list = get_all_addresses_from_header(&report.headers, |header_key| {
header_key == "x-failed-recipients"
});
let mut to_list =
get_all_addresses_from_header(&report.headers, "x-failed-recipients");
let to = if to_list.len() == 1 {
Some(to_list.pop().unwrap())
} else {
@@ -2056,36 +2055,48 @@ fn get_attachment_filename(
/// Returned addresses are normalized and lowercased.
pub(crate) fn get_recipients(headers: &[MailHeader]) -> Vec<SingleInfo> {
get_all_addresses_from_header(headers, |header_key| {
header_key == "to" || header_key == "cc"
})
let to_addresses = get_all_addresses_from_header(headers, "to");
let cc_addresses = get_all_addresses_from_header(headers, "cc");
let mut res = to_addresses;
res.extend(cc_addresses);
res
}
/// Returned addresses are normalized and lowercased.
pub(crate) fn get_from(headers: &[MailHeader]) -> Option<SingleInfo> {
let all = get_all_addresses_from_header(headers, |header_key| header_key == "from");
let all = get_all_addresses_from_header(headers, "from");
tools::single_value(all)
}
/// Returned addresses are normalized and lowercased.
pub(crate) fn get_list_post(headers: &[MailHeader]) -> Option<String> {
get_all_addresses_from_header(headers, |header_key| header_key == "list-post")
get_all_addresses_from_header(headers, "list-post")
.into_iter()
.next()
.map(|s| s.addr)
}
fn get_all_addresses_from_header(
headers: &[MailHeader],
pred: fn(String) -> bool,
) -> Vec<SingleInfo> {
/// Extracts all addresses from the header named `header`.
///
/// If multiple headers with the same name are present,
/// the last one is taken.
/// This is because DKIM-Signatures apply to the last
/// headers, and more headers may be added
/// to the beginning of the messages
/// without invalidating the signature
/// unless the header is "oversigned",
/// i.e. included in the signature more times
/// than it appears in the mail.
fn get_all_addresses_from_header(headers: &[MailHeader], header: &str) -> Vec<SingleInfo> {
let mut result: Vec<SingleInfo> = Default::default();
headers
if let Some(header) = headers
.iter()
.filter(|header| pred(header.get_key().to_lowercase()))
.filter_map(|header| mailparse::addrparse_header(header).ok())
.for_each(|addrs| {
.rev()
.find(|h| h.get_key().to_lowercase() == header)
{
if let Ok(addrs) = mailparse::addrparse_header(header) {
for addr in addrs.iter() {
match addr {
mailparse::MailAddr::Single(ref info) => {
@@ -2104,7 +2115,8 @@ fn get_all_addresses_from_header(
}
}
}
});
}
}
result
}
@@ -2404,6 +2416,22 @@ mod tests {
assert!(recipients.iter().any(|info| info.addr == "abc@bcd.com"));
assert!(recipients.iter().any(|info| info.addr == "def@def.de"));
assert_eq!(recipients.len(), 2);
// If some header is present multiple times,
// only the last one must be used.
let raw = b"From: alice@example.org\n\
TO: mallory@example.com\n\
To: mallory@example.net\n\
To: bob@example.net\n\
Content-Type: text/plain\n\
Chat-Version: 1.0\n\
\n\
Hello\n\
";
let mail = mailparse::parse_mail(&raw[..]).unwrap();
let recipients = get_recipients(&mail.headers);
assert!(recipients.iter().any(|info| info.addr == "bob@example.net"));
assert_eq!(recipients.len(), 1);
}
#[test]
@@ -3986,4 +4014,43 @@ Content-Type: text/plain; charset=utf-8
// Not "Some subject /help"
assert_eq!(message.parts[0].msg, "/help");
}
/// Tests that Delta Chat takes the last header value
/// rather than the first one if multiple headers
/// are present.
///
/// DKIM signature applies to the last N headers
/// if header name is included N times in
/// DKIM-Signature.
///
/// If the client takes the first header
/// rather than the last, it can be fooled
/// into using unsigned header
/// when signed one is present
/// but not protected by oversigning.
///
/// See
/// <https://www.zone.eu/blog/2024/05/17/bimi-and-dmarc-cant-save-you/>
/// for reference.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_take_last_header() {
let context = TestContext::new().await;
// Mallory added second From: header.
let raw = b"From: mallory@example.org\n\
From: alice@example.org\n\
Content-Type: text/plain\n\
Chat-Version: 1.0\n\
\n\
Hello\n\
";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None)
.await
.unwrap();
assert_eq!(
mimeparser.get_header(HeaderDef::From_).unwrap(),
"alice@example.org"
);
}
}

View File

@@ -29,7 +29,7 @@ use futures_lite::StreamExt;
use iroh_gossip::net::{Gossip, JoinTopicFut, GOSSIP_ALPN};
use iroh_gossip::proto::{Event as IrohEvent, TopicId};
use iroh_net::relay::{RelayMap, RelayUrl};
use iroh_net::{key::SecretKey, relay::RelayMode, MagicEndpoint};
use iroh_net::{key::SecretKey, relay::RelayMode, Endpoint};
use iroh_net::{NodeAddr, NodeId};
use std::collections::{BTreeSet, HashMap};
use std::env;
@@ -51,8 +51,8 @@ const PUBLIC_KEY_STUB: &[u8] = "static_string".as_bytes();
/// Store iroh peer channels for the context.
#[derive(Debug)]
pub struct Iroh {
/// [MagicEndpoint] needed for iroh peer channels.
pub(crate) endpoint: MagicEndpoint,
/// [Endpoint] needed for iroh peer channels.
pub(crate) endpoint: Endpoint,
/// [Gossip] needed for iroh peer channels.
pub(crate) gossip: Gossip,
@@ -81,7 +81,14 @@ impl Iroh {
msg_id: MsgId,
) -> Result<Option<JoinTopicFut>> {
let topic = get_iroh_topic_for_msg(ctx, msg_id).await?;
let seq = if let Some(channel_state) = self.iroh_channels.read().await.get(&topic) {
// Take exclusive lock to make sure
// no other thread can create a second gossip subscription
// after we check that it does not exist and before we create a new one.
// Otherwise we would receive every message twice or more times.
let mut iroh_channels = self.iroh_channels.write().await;
let seq = if let Some(channel_state) = iroh_channels.get(&topic) {
if channel_state.subscribe_loop.is_some() {
return Ok(None);
}
@@ -102,6 +109,11 @@ impl Iroh {
self.endpoint.add_node_addr(peer.clone())?;
}
let connect_future = self
.gossip
.join(topic, peers.into_iter().map(|addr| addr.node_id).collect())
.await?;
let ctx = ctx.clone();
let gossip = self.gossip.clone();
let subscribe_loop = tokio::spawn(async move {
@@ -110,15 +122,7 @@ impl Iroh {
}
});
let connect_future = self
.gossip
.join(topic, peers.into_iter().map(|addr| addr.node_id).collect())
.await?;
self.iroh_channels
.write()
.await
.insert(topic, ChannelState::new(seq, subscribe_loop));
iroh_channels.insert(topic, ChannelState::new(seq, subscribe_loop));
Ok(Some(connect_future))
}
@@ -154,10 +158,6 @@ impl Iroh {
self.gossip.broadcast(topic, data.into()).await?;
if env::var("REALTIME_DEBUG").is_ok() {
info!(ctx, "Sent realtime data");
}
Ok(())
}
@@ -226,7 +226,7 @@ impl Context {
RelayMode::Default
};
let endpoint = MagicEndpoint::builder()
let endpoint = Endpoint::builder()
.secret_key(secret_key.clone())
.alpns(vec![GOSSIP_ALPN.to_vec()])
.relay_mode(relay_mode)
@@ -242,6 +242,7 @@ impl Context {
// Shuts down on deltachat shutdown
tokio::spawn(endpoint_loop(context, endpoint.clone(), gossip.clone()));
let endp = endpoint.clone();
let gsp = gossip.clone();
tokio::spawn(async move {
@@ -352,7 +353,9 @@ pub async fn send_webxdc_realtime_advertisement(
msg.param.set_cmd(SystemMessage::IrohNodeAddr);
msg.in_reply_to = Some(webxdc.rfc724_mid.clone());
send_msg(ctx, webxdc.chat_id, &mut msg).await?;
info!(ctx, "IROH_REALTIME: Sent realtime advertisement");
if env::var("REALTIME_DEBUG").is_ok() {
info!(ctx, "IROH_REALTIME: Sent realtime advertisement");
}
Ok(conn)
}
@@ -368,6 +371,7 @@ pub async fn leave_webxdc_realtime(ctx: &Context, msg_id: MsgId) -> Result<()> {
let iroh = ctx.get_or_try_init_peer_channel().await?;
iroh.leave_realtime(get_iroh_topic_for_msg(ctx, msg_id).await?)
.await?;
info!(ctx, "IROH_REALTIME: Left gossip for message {msg_id}");
Ok(())
@@ -389,7 +393,7 @@ pub(crate) async fn create_iroh_header(
))
}
async fn endpoint_loop(context: Context, endpoint: MagicEndpoint, gossip: Gossip) {
async fn endpoint_loop(context: Context, endpoint: Endpoint, gossip: Gossip) {
while let Some(conn) = endpoint.accept().await {
info!(context, "IROH_REALTIME: accepting iroh connection");
let gossip = gossip.clone();
@@ -404,12 +408,12 @@ async fn endpoint_loop(context: Context, endpoint: MagicEndpoint, gossip: Gossip
async fn handle_connection(
context: &Context,
mut conn: iroh_net::magic_endpoint::Connecting,
mut conn: iroh_net::endpoint::Connecting,
gossip: Gossip,
) -> anyhow::Result<()> {
let alpn = conn.alpn().await?;
let conn = conn.await?;
let peer_id = iroh_net::magic_endpoint::get_remote_node_id(&conn)?;
let peer_id = iroh_net::endpoint::get_remote_node_id(&conn)?;
match alpn.as_bytes() {
GOSSIP_ALPN => gossip
@@ -435,11 +439,12 @@ async fn subscribe_loop(
let event = stream.recv().await?;
match event {
IrohEvent::NeighborUp(node) => {
info!(context, "IROH_REALTIME: NeighborUp: {}", node.to_string());
iroh_add_peer_for_topic(context, msg_id, topic, node, None).await?;
}
IrohEvent::Received(event) => {
info!(context, "IROH_REALTIME: Received realtime data");
if env::var("REALTIME_DEBUG").is_ok() {
info!(context, "IROH_REALTIME: Received realtime data");
}
context.emit_event(EventType::WebxdcRealtimeData {
msg_id,
data: event
@@ -775,7 +780,7 @@ mod tests {
alice.recv_msg_trash(&bob_advertisement).await;
eprintln!("Alice waits for connection");
alice_advertisement_future.await;
alice_advertisement_future.await.unwrap();
// Alice sends ephemeral message
eprintln!("Sending ephemeral message");

View File

@@ -40,7 +40,7 @@ use crate::simplify;
use crate::sql;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{self, buf_compress, extract_grpid_from_rfc724_mid};
use crate::tools::{self, buf_compress};
use crate::{chatlist_events, location};
use crate::{contact, imap};
use iroh_net::NodeAddr;
@@ -755,14 +755,21 @@ async fn add_parts(
let state: MessageState;
let mut hidden = false;
let mut needs_delete_job = false;
// if contact renaming is prevented (for mailinglists and bots),
// we use name from From:-header as override name
if prevent_rename {
if let Some(name) = &mime_parser.from.display_name {
for part in &mut mime_parser.parts {
part.param.set(Param::OverrideSenderDisplayname, name);
}
}
}
if mime_parser.incoming {
to_id = ContactId::SELF;
let test_normal_chat = if from_id == ContactId::UNDEFINED {
None
} else {
ChatIdBlocked::lookup_by_contact(context, from_id).await?
};
let test_normal_chat = ChatIdBlocked::lookup_by_contact(context, from_id).await?;
if chat_id.is_none() && mime_parser.delivery_report.is_some() {
chat_id = Some(DC_CHAT_ID_TRASH);
@@ -827,18 +834,13 @@ async fn add_parts(
create_blocked_default
};
if chat_id.is_none() && !is_mdn {
if chat_id.is_none() && (allow_creation || test_normal_chat.is_some()) {
// try to create a group
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
if let Some((new_chat_id, new_chat_id_blocked)) = create_group(
context,
mime_parser,
is_partial_download.is_some(),
if test_normal_chat.is_none() {
allow_creation
} else {
true
},
create_blocked,
from_id,
to_ids,
@@ -860,7 +862,7 @@ async fn add_parts(
}
}
// In lookup_chat_by_reply() and create_or_lookup_group(), it can happen that the message is put into a chat
// In lookup_chat_by_reply() and create_group(), it can happen that the message is put into a chat
// but the From-address is not a member of this chat.
if let Some(group_chat_id) = chat_id {
if !chat::is_contact_in_chat(context, group_chat_id, from_id).await? {
@@ -918,16 +920,6 @@ async fn add_parts(
apply_mailinglist_changes(context, mime_parser, chat_id).await?;
}
// if contact renaming is prevented (for mailinglists and bots),
// we use name from From:-header as override name
if prevent_rename {
if let Some(name) = &mime_parser.from.display_name {
for part in &mut mime_parser.parts {
part.param.set(Param::OverrideSenderDisplayname, name);
}
}
}
if chat_id.is_none() {
// try to create a normal chat
let create_blocked = if from_id == ContactId::SELF {
@@ -963,7 +955,7 @@ async fn add_parts(
if create_blocked == Blocked::Request && parent.is_some() {
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
// the contact requests will pop up and this should be just fine.
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo)
ContactId::scaleup_origin(context, &[from_id], Origin::IncomingReplyTo)
.await?;
info!(
context,
@@ -1018,7 +1010,6 @@ async fn add_parts(
|| fetching_existing_messages
|| is_mdn
|| is_reaction
|| is_location_kml
|| chat_id_blocked == Blocked::Yes
{
MessageState::InSeen
@@ -1108,12 +1099,11 @@ async fn add_parts(
}
if !to_ids.is_empty() {
if chat_id.is_none() {
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
if chat_id.is_none() && allow_creation {
if let Some((new_chat_id, new_chat_id_blocked)) = create_group(
context,
mime_parser,
is_partial_download.is_some(),
allow_creation,
Blocked::Not,
from_id,
to_ids,
@@ -1826,37 +1816,32 @@ async fn is_probably_private_reply(
Ok(true)
}
/// This function tries to extract the group-id from the message and returns the corresponding
/// chat_id. If the chat does not exist, it is created. If there is no group-id and there are more
/// This function tries to extract the group-id from the message and create a new group
/// chat with this ID. If there is no group-id and there are more
/// than two members, a new ad hoc group is created.
///
/// On success the function returns the found/created (chat_id, chat_blocked) tuple.
#[allow(clippy::too_many_arguments)]
async fn create_or_lookup_group(
async fn create_group(
context: &Context,
mime_parser: &mut MimeMessage,
is_partial_download: bool,
allow_creation: bool,
create_blocked: Blocked,
from_id: ContactId,
to_ids: &[ContactId],
verified_encryption: &VerifiedEncryption,
) -> Result<Option<(ChatId, Blocked)>> {
let grpid = if let Some(grpid) = try_getting_grpid(mime_parser) {
grpid
} else if !allow_creation {
info!(context, "Creating ad-hoc group prevented from caller.");
return Ok(None);
} else if is_partial_download {
// Partial download may be an encrypted message with protected Subject header.
//
// We do not want to create a group with "..." or "Encrypted message" as a subject.
info!(
context,
"Ad-hoc group cannot be created from partial download."
);
return Ok(None);
} else {
let Some(grpid) = mime_parser.get_chat_group_id().map(|s| s.to_string()) else {
if is_partial_download {
// Partial download may be an encrypted message with protected Subject header.
//
// We do not want to create a group with "..." or "Encrypted message" as a subject.
info!(
context,
"Ad-hoc group cannot be created from partial download."
);
return Ok(None);
}
let mut member_ids: Vec<ContactId> = to_ids.to_vec();
if !member_ids.contains(&(from_id)) {
member_ids.push(from_id);
@@ -1872,15 +1857,8 @@ async fn create_or_lookup_group(
return Ok(res);
};
let mut chat_id;
let mut chat_id_blocked;
if let Some((id, _protected, blocked)) = chat::get_chat_id_by_grpid(context, &grpid).await? {
chat_id = Some(id);
chat_id_blocked = blocked;
} else {
chat_id = None;
chat_id_blocked = Default::default();
}
let mut chat_id = None;
let mut chat_id_blocked = Default::default();
// For chat messages, we don't have to guess (is_*probably*_private_reply()) but we know for sure that
// they belong to the group because of the Chat-Group-Id or Message-Id header
@@ -1925,11 +1903,6 @@ async fn create_or_lookup_group(
|| self_explicitly_added(context, &mime_parser).await?)
{
// Group does not exist but should be created.
if !allow_creation {
info!(context, "Creating group forbidden by caller.");
return Ok(None);
}
let grpname = mime_parser
.get_header(HeaderDef::ChatGroupName)
.context("Chat-Group-Name vanished")?
@@ -2492,37 +2465,6 @@ async fn apply_mailinglist_changes(
Ok(())
}
fn try_getting_grpid(mime_parser: &MimeMessage) -> Option<String> {
if let Some(optional_field) = mime_parser.get_chat_group_id() {
return Some(optional_field.to_string());
}
// Useful for undecipherable messages sent to known group.
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::MessageId) {
return Some(extracted_grpid.to_string());
}
if !mime_parser.has_chat_version() {
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
return Some(extracted_grpid.to_string());
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References) {
return Some(extracted_grpid.to_string());
}
}
None
}
/// try extract a grpid from a message-id list header value
fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str> {
let header = mime_parser.get_header(headerdef)?;
let parts = header
.split(',')
.map(str::trim)
.filter(|part| !part.is_empty());
parts.filter_map(extract_grpid_from_rfc724_mid).next()
}
/// Creates ad-hoc group and returns chat ID on success.
async fn create_adhoc_group(
context: &Context,

View File

@@ -16,25 +16,6 @@ use crate::imex::{imex, ImexMode};
use crate::test_utils::{get_chat_msg, TestContext, TestContextManager};
use crate::tools::SystemTime;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_grpid_simple() {
let context = TestContext::new_alice().await;
let raw = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
From: hello@example.org\n\
Subject: outer-subject\n\
In-Reply-To: <lqkjwelq123@123123>\n\
References: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\n\
\n\
hello\x00";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None)
.await
.unwrap();
assert_eq!(mimeparser.incoming, true);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
let grpid = Some("HcxyMARjyJy");
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_outgoing() -> Result<()> {
let context = TestContext::new_alice().await;
@@ -61,24 +42,6 @@ async fn test_bad_from() {
assert!(mimeparser.is_err());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_grpid_from_multiple() {
let context = TestContext::new_alice().await;
let raw = b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
From: hello@example.org\n\
Subject: outer-subject\n\
In-Reply-To: <Gr.HcxyMARjyJy.9-qweqwe@asd.net>\n\
References: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\n\
\n\
hello\x00";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..], None)
.await
.unwrap();
let grpid = Some("HcxyMARjyJy");
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
}
static MSGRMSG: &[u8] =
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
From: Bob <bob@example.com>\n\

View File

@@ -407,7 +407,7 @@ async fn inbox_loop(
} else {
match connection.prepare(&ctx).await {
Err(err) => {
warn!(ctx, "Failed to prepare connection: {:#}.", err);
warn!(ctx, "Failed to prepare INBOX connection: {:#}.", err);
continue;
}
Ok(session) => session,
@@ -714,7 +714,10 @@ async fn simple_imap_loop(
} else {
match connection.prepare(&ctx).await {
Err(err) => {
warn!(ctx, "Failed to prepare connection: {:#}.", err);
warn!(
ctx,
"Failed to prepare {folder_meaning} connection: {err:#}."
);
continue;
}
Ok(session) => session,

View File

@@ -447,7 +447,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore);
}
contact_id.regossip_keys(context).await?;
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await?;
ContactId::scaleup_origin(context, &[contact_id], Origin::SecurejoinInvited).await?;
info!(context, "Auth verified.",);
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
inviter_progress(context, contact_id, 600);
@@ -757,9 +757,9 @@ mod tests {
use deltachat_contact_tools::{ContactAddress, EmailAddress};
use super::*;
use crate::chat::remove_contact_from_chat;
use crate::chat::{remove_contact_from_chat, CantSendReason};
use crate::chatlist::Chatlist;
use crate::constants::Chattype;
use crate::constants::{self, Chattype};
use crate::imex::{imex, ImexMode};
use crate::receive_imf::receive_imf;
use crate::stock_str::{self, chat_protection_enabled};
@@ -774,6 +774,7 @@ mod tests {
Normal,
CheckProtectionTimestamp,
WrongAliceGossip,
SecurejoinWaitTimeout,
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -791,6 +792,11 @@ mod tests {
test_setup_contact_ex(SetupContactCase::WrongAliceGossip).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_setup_contact_wait_timeout() {
test_setup_contact_ex(SetupContactCase::SecurejoinWaitTimeout).await
}
async fn test_setup_contact_ex(case: SetupContactCase) {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
@@ -850,9 +856,21 @@ mod tests {
msg.get_header(HeaderDef::SecureJoin).unwrap(),
"vc-auth-required"
);
let bob_chat = bob.create_chat(&alice).await;
assert_eq!(bob_chat.can_send(&bob).await.unwrap(), false);
assert_eq!(
bob_chat.why_cant_send(&bob).await.unwrap(),
Some(CantSendReason::SecurejoinWait)
);
if case == SetupContactCase::SecurejoinWaitTimeout {
SystemTime::shift(Duration::from_secs(constants::SECUREJOIN_WAIT_TIMEOUT));
assert_eq!(bob_chat.can_send(&bob).await.unwrap(), true);
}
// Step 4: Bob receives vc-auth-required, sends vc-request-with-auth
bob.recv_msg_trash(&sent).await;
let bob_chat = bob.create_chat(&alice).await;
assert_eq!(bob_chat.can_send(&bob).await.unwrap(), true);
// Check Bob emitted the JoinerProgress event.
let event = bob
@@ -986,12 +1004,36 @@ mod tests {
bob.recv_msg_trash(&sent).await;
assert_eq!(contact_alice.is_verified(&bob.ctx).await.unwrap(), true);
// Check Bob got the verified message in his 1:1 chat.
let chat = bob.create_chat(&alice).await;
let msg = get_chat_msg(&bob, chat.get_id(), 0, 2).await;
if case != SetupContactCase::SecurejoinWaitTimeout {
// Later we check that the timeout message isn't added to the already protected chat.
SystemTime::shift(Duration::from_secs(constants::SECUREJOIN_WAIT_TIMEOUT + 1));
assert_eq!(
bob_chat
.check_securejoin_wait(&bob, constants::SECUREJOIN_WAIT_TIMEOUT)
.await
.unwrap(),
0
);
}
// Check Bob got expected info messages in his 1:1 chat.
let msg_cnt: usize = match case {
SetupContactCase::SecurejoinWaitTimeout => 3,
_ => 2,
};
let mut i = 0..msg_cnt;
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob).await);
let msg = get_chat_msg(&bob, chat.get_id(), 1, 2).await;
if case == SetupContactCase::SecurejoinWaitTimeout {
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
assert!(msg.is_info());
assert_eq!(
msg.get_text(),
stock_str::securejoin_wait_timeout(&bob).await
);
}
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), chat_protection_enabled(&bob).await);
}

View File

@@ -14,7 +14,7 @@ use super::qrinvite::QrInvite;
use super::{encrypted_and_signed, verify_sender_by_fingerprint};
use crate::chat::{self, ChatId};
use crate::config::Config;
use crate::contact::{Contact, Origin};
use crate::contact::{ContactId, Origin};
use crate::context::Context;
use crate::events::EventType;
use crate::headerdef::HeaderDef;
@@ -326,8 +326,12 @@ impl BobState {
Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
peerstate.save_to_db(&context.sql).await?;
Contact::scaleup_origin_by_id(context, self.invite.contact_id(), Origin::SecurejoinJoined)
.await?;
ContactId::scaleup_origin(
context,
&[self.invite.contact_id()],
Origin::SecurejoinJoined,
)
.await?;
context.emit_event(EventType::ContactsChanged(None));
self.update_next(&context.sql, SecureJoinStep::Completed)

View File

@@ -137,9 +137,9 @@ ALTER TABLE acpeerstates ADD COLUMN gossip_key;"#,
// the current ones are defined by chats.blocked=2
sql.execute_migration(
r#"
DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;"
CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);"
ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;")
DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;"#,
27,
)
@@ -267,7 +267,7 @@ CREATE INDEX msgs_index6 ON msgs (location_id);"#,
// so, msg_id may or may not exist.
sql.execute_migration(
r#"
CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);",
CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);
CREATE INDEX devmsglabels_index1 ON devmsglabels (label);"#, 59)
.await?;
if exists_before_update && sql.get_raw_config_int("bcc_self").await?.is_none() {

View File

@@ -294,31 +294,6 @@ pub(crate) fn create_outgoing_rfc724_mid() -> String {
format!("Mr.{}.{}@localhost", create_id(), create_id())
}
/// Extract the group id (grpid) from a message id (mid)
///
/// # Arguments
///
/// * `mid` - A string that holds the message id. Leading/Trailing <>
/// characters are automatically stripped.
pub(crate) fn extract_grpid_from_rfc724_mid(mid: &str) -> Option<&str> {
let mid = mid.trim_start_matches('<').trim_end_matches('>');
if mid.len() < 9 || !mid.starts_with("Gr.") {
return None;
}
if let Some(mid_without_offset) = mid.get(3..) {
if let Some(grpid_len) = mid_without_offset.find('.') {
/* strict length comparison, the 'Gr.' magic is weak enough */
if grpid_len == 11 || grpid_len == 16 {
return Some(mid_without_offset.get(0..grpid_len).unwrap());
}
}
}
None
}
// the returned suffix is lower-case
pub fn get_filesuffix_lc(path_filename: &str) -> Option<String> {
Path::new(path_filename)
@@ -925,45 +900,11 @@ DKIM Results: Passed=true";
}
}
#[test]
fn test_extract_grpid_from_rfc724_mid() {
// Should return None if we pass invalid mid
let mid = "foobar";
let grpid = extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, None);
// Should return None if grpid has a length which is not 11 or 16
let mid = "Gr.12345678.morerandom@domain.de";
let grpid = extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, None);
// Should return extracted grpid for grpid with length of 11
let mid = "Gr.12345678901.morerandom@domain.de";
let grpid = extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("12345678901"));
// Should return extracted grpid for grpid with length of 11
let mid = "Gr.1234567890123456.morerandom@domain.de";
let grpid = extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("1234567890123456"));
// Should return extracted grpid for grpid with length of 11
let mid = "<Gr.12345678901.morerandom@domain.de>";
let grpid = extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("12345678901"));
// Should return extracted grpid for grpid with length of 11
let mid = "<Gr.1234567890123456.morerandom@domain.de>";
let grpid = extract_grpid_from_rfc724_mid(mid);
assert_eq!(grpid, Some("1234567890123456"));
}
#[test]
fn test_create_outgoing_rfc724_mid() {
let mid = create_outgoing_rfc724_mid();
assert!(mid.starts_with("Mr."));
assert!(mid.ends_with("@localhost"));
assert!(extract_grpid_from_rfc724_mid(mid.as_str()).is_none());
}
proptest! {