Compare commits

..

10 Commits

Author SHA1 Message Date
adbenitez
2a4d352dff add example 2023-10-27 01:33:21 +02:00
adbenitez
8329108c47 re-use get_async_rpc() 2023-10-26 23:34:32 +02:00
adbenitez
6d2bae7b1d allow to use the JSON RPC client from async code 2023-10-26 23:30:13 +02:00
link2xt
1b66120e7d chore(release): prepare for 1.127.0 2023-10-26 15:54:48 +00:00
link2xt
1478f321ae fix: restore try_many_times workaround
Even though r2d2 connection pool is removed,
deleting accounts still fails in Windows CI.

This reverts commit e88f21c010.
`try_many_times` documentation is modified to explain
why the workaround is still needed.
2023-10-26 15:15:44 +00:00
link2xt
5fb92c78ad ci: test deltachat-rpc-client on Windows 2023-10-26 15:15:44 +00:00
link2xt
a0a792b821 chore: update sct, serde and serde_derive 2023-10-26 14:06:44 +00:00
link2xt
3feb0e648d build: switch to iroh 0.4.x fork with updated dependencies 2023-10-26 14:04:32 +00:00
link2xt
fa5358a5bf chore: update tracing 2023-10-26 13:17:54 +00:00
Sebastian Klähn
7399a398a7 api: add mailto parse api (#4829)
close #4620 

This PR introduces a new core API to parse mailto links into a uniform
data format. This could be used to unify the different implementations
on the current platforms.
To complete this PR we have to decide for which APIs we want to expose
this (now) internal API (c, python, json-rpc, etc.), and if we want such
an API at all as it doesn't have a corresponding UI-PR and is not
_really_ needed.
2023-10-26 11:46:51 +02:00
20 changed files with 408 additions and 284 deletions

View File

@@ -130,7 +130,7 @@ jobs:
name: Build deltachat-rpc-server
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@@ -145,7 +145,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}-deltachat-rpc-server
path: target/debug/deltachat-rpc-server
path: ${{ matrix.os == 'windows-latest' && 'target/debug/deltachat-rpc-server.exe' || 'target/debug/deltachat-rpc-server' }}
retention-days: 1
python_lint:
@@ -228,6 +228,8 @@ jobs:
python: 3.12
- os: macos-latest
python: 3.12
- os: windows-latest
python: 3.12
# PyPy tests
- os: ubuntu-latest
@@ -258,11 +260,18 @@ jobs:
path: target/debug
- name: Make deltachat-rpc-server executable
if: ${{ matrix.os != 'windows-latest' }}
run: chmod +x target/debug/deltachat-rpc-server
- name: Add deltachat-rpc-server to path
if: ${{ matrix.os != 'windows-latest' }}
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
- name: Add deltachat-rpc-server to path
if: ${{ matrix.os == 'windows-latest' }}
run: |
"${{ github.workspace }}/target/debug" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Run deltachat-rpc-client tests
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}

View File

@@ -1,5 +1,62 @@
# Changelog
## [1.127.0] - 2023-10-26
### API-Changes
- [**breaking**] `dc_accounts_new` API is changed. Unused `os_name` argument is removed and `writable` argument is added.
- jsonrpc: Add `resend_messages`.
- [**breaking**] Remove unused function `is_verified_ex()` ([#4551](https://github.com/deltachat/deltachat-core-rust/pull/4551))
- [**breaking**] Make `MsgId.delete_from_db()` private.
- [**breaking**] deltachat-jsonrpc: use `kind` as a tag for all union types
- json-rpc: Force stickers to be sent as stickers ([#4819](https://github.com/deltachat/deltachat-core-rust/pull/4819)).
- Add mailto parse api ([#4829](https://github.com/deltachat/deltachat-core-rust/pull/4829)).
- [**breaking**] Remove unused `DC_STR_PROTECTION_(EN)ABLED` strings
- [**breaking**] Remove unused `dc_set_chat_protection()`
- Hide `DcSecretKey` trait from the API.
- Verified 1:1 chats ([#4315](https://github.com/deltachat/deltachat-core-rust/pull/4315)). Disabled by default, enable with `verified_one_on_one_chats` config.
### CI
- Run Rust tests with `RUST_BACKTRACE` set.
- Replace `master` branch with `main`. Run CI only on `main` branch pushes.
- Test `deltachat-rpc-client` on Windows.
### Documentation
- Document how logs and error messages should be formatted in `CONTRIBUTING.md`.
- Clarify transitive behaviour of `dc_contact_is_verfified()`.
- Document `configured_addr`.
### Features / Changes
- Add lockfile to account manager ([#4314](https://github.com/deltachat/deltachat-core-rust/pull/4314)).
- Don't show a contact as verified if their key changed since the verification ([#4574](https://github.com/deltachat/deltachat-core-rust/pull/4574)).
- deltachat-rpc-server: Add `--openrpc` option to print OpenRPC specification for JSON-RPC API. This specification can be used to generate JSON-RPC API clients.
- Track whether contact is a bot or not ([#4821](https://github.com/deltachat/deltachat-core-rust/pull/4821)).
- Replace `Config::SendSyncMsgs` with `SyncMsgs` ([#4817](https://github.com/deltachat/deltachat-core-rust/pull/4817)).
### Fixes
- Don't create 1:1 chat as protected for contact who doesn't prefer to encrypt ([#4538](https://github.com/deltachat/deltachat-core-rust/pull/4538)).
- Allow to save a draft if the verification is broken ([#4542](https://github.com/deltachat/deltachat-core-rust/pull/4542)).
- Fix info-message orderings of verified 1:1 chats ([#4545](https://github.com/deltachat/deltachat-core-rust/pull/4545)).
- Fix example; this was changed some time ago, see https://docs.webxdc.org/spec.html#sendupdate
- `receive_imf`: Update peerstate from db after handling Securejoin handshake ([#4600](https://github.com/deltachat/deltachat-core-rust/pull/4600)).
- Sort old incoming messages below all outgoing ones ([#4621](https://github.com/deltachat/deltachat-core-rust/pull/4621)).
- Do not mark non-verified group chats as verified when using securejoin.
- `receive_imf`: Set protection only for Chattype::Single ([#4597](https://github.com/deltachat/deltachat-core-rust/pull/4597)).
- Return from `dc_get_chatlist(DC_GCL_FOR_FORWARDING)` only chats where we can send ([#4616](https://github.com/deltachat/deltachat-core-rust/pull/4616)).
- Clear VerifiedOneOnOneChats config on backup ([#4615](https://github.com/deltachat/deltachat-core-rust/pull/4615)).
- Try removal of accounts multiple times with timeouts in case the database file is blocked (restore `try_many_times` workaround).
### Build system
- Remove examples/simple.rs.
- Increase MSRV to 1.70.0.
- Update dependencies.
- Switch to iroh 0.4.x fork with updated dependencies.
## [1.126.1] - 2023-10-24
### Fixes
@@ -2967,3 +3024,5 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.124.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.124.0...v1.124.1
[1.125.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.124.1...v1.125.0
[1.126.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.125.0...v1.126.0
[1.126.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.126.0...v1.126.1
[1.127.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.126.1...v1.127.0

233
Cargo.lock generated
View File

@@ -652,7 +652,7 @@ dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.48.5",
"windows-targets",
]
[[package]]
@@ -1074,7 +1074,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.126.1"
version = "1.127.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1151,7 +1151,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.126.1"
version = "1.127.0"
dependencies = [
"anyhow",
"async-channel",
@@ -1175,7 +1175,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.126.1"
version = "1.127.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1190,7 +1190,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.126.1"
version = "1.127.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1215,7 +1215,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.126.1"
version = "1.127.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1397,7 +1397,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -1748,7 +1748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -1824,7 +1824,7 @@ checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -1871,7 +1871,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.3.5",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -2245,7 +2245,7 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -2469,7 +2469,7 @@ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2 0.5.5",
"widestring",
"windows-sys 0.48.0",
"windows-sys",
"winreg",
]
@@ -2482,8 +2482,7 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "iroh"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4fb9858c8cd3dd924a5da5bc511363845a9bcfdfac066bb2ef8454eb6111546"
source = "git+https://github.com/deltachat/iroh?branch=0.4-update-quic#36ca9ca017a101c00dfdf74a917b92bdc505ddbd"
dependencies = [
"abao",
"anyhow",
@@ -2504,8 +2503,9 @@ dependencies = [
"quinn",
"rand 0.7.3",
"rcgen",
"ring",
"ring 0.16.20",
"rustls",
"rustls-webpki",
"serde",
"serde-error",
"ssh-key",
@@ -2518,7 +2518,6 @@ dependencies = [
"tracing-futures",
"tracing-subscriber",
"walkdir",
"webpki",
"x509-parser",
"zeroize",
]
@@ -2531,7 +2530,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -2769,7 +2768,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -3180,7 +3179,7 @@ dependencies = [
"libc",
"redox_syscall 0.3.5",
"smallvec",
"windows-targets 0.48.5",
"windows-targets",
]
[[package]]
@@ -3529,11 +3528,12 @@ checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142"
[[package]]
name = "quic-rpc"
version = "0.5.2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d453504fc3e456160ae3b9ebe4d83c1f6477af167aa9b67e2d7bf11a096f179d"
checksum = "6d60c2fc2390baad4b9d41ae9957ae88c3095496f88e252ef50722df8b5b78d7"
dependencies = [
"bincode",
"educe",
"flume",
"futures",
"pin-project",
@@ -3562,9 +3562,9 @@ dependencies = [
[[package]]
name = "quinn"
version = "0.9.3"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1"
checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75"
dependencies = [
"bytes",
"pin-project-lite",
@@ -3575,17 +3575,17 @@ dependencies = [
"thiserror",
"tokio",
"tracing",
"webpki",
]
[[package]]
name = "quinn-proto"
version = "0.9.2"
source = "git+https://github.com/quinn-rs/quinn?branch=main#11b34a7b2652010cdbbd08b5dfa407832baff927"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c78e758510582acc40acb90458401172d41f1016f8c9dde89e49677afb7eec1"
dependencies = [
"bytes",
"rand 0.8.5",
"ring",
"ring 0.16.20",
"rustc-hash",
"rustls",
"rustls-native-certs",
@@ -3593,19 +3593,19 @@ dependencies = [
"thiserror",
"tinyvec",
"tracing",
"webpki",
]
[[package]]
name = "quinn-udp"
version = "0.3.2"
source = "git+https://github.com/quinn-rs/quinn?branch=main#11b34a7b2652010cdbbd08b5dfa407832baff927"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7"
dependencies = [
"bytes",
"libc",
"quinn-proto",
"socket2 0.4.10",
"socket2 0.5.5",
"tracing",
"windows-sys 0.45.0",
"windows-sys",
]
[[package]]
@@ -3744,7 +3744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b"
dependencies = [
"pem",
"ring",
"ring 0.16.20",
"time 0.3.30",
"yasna",
]
@@ -3907,11 +3907,25 @@ dependencies = [
"libc",
"once_cell",
"spin 0.5.2",
"untrusted",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]]
name = "ring"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
dependencies = [
"cc",
"getrandom 0.2.10",
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
"windows-sys",
]
[[package]]
name = "ripemd"
version = "0.1.3"
@@ -4024,18 +4038,18 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
name = "rustls"
version = "0.20.9"
version = "0.21.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
dependencies = [
"ring",
"ring 0.17.5",
"rustls-webpki",
"sct",
"webpki",
]
[[package]]
@@ -4059,6 +4073,16 @@ dependencies = [
"base64 0.21.5",
]
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring 0.17.5",
"untrusted 0.9.0",
]
[[package]]
name = "rustversion"
version = "1.0.14"
@@ -4125,7 +4149,7 @@ version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -4160,12 +4184,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
"ring 0.17.5",
"untrusted 0.9.0",
]
[[package]]
@@ -4236,9 +4260,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.189"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
dependencies = [
"serde_derive",
]
@@ -4263,9 +4287,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.189"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
@@ -4463,7 +4487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -4670,7 +4694,7 @@ dependencies = [
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -4818,7 +4842,7 @@ dependencies = [
"signal-hook-registry",
"socket2 0.5.5",
"tokio-macros",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -4983,11 +5007,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
@@ -4996,9 +5019,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.26"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
@@ -5007,9 +5030,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.31"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
@@ -5175,6 +5198,12 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.4.1"
@@ -5345,16 +5374,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "weezl"
version = "0.1.7"
@@ -5427,16 +5446,7 @@ version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.1",
"windows-targets",
]
[[package]]
@@ -5445,22 +5455,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
"windows-targets",
]
[[package]]
@@ -5469,21 +5464,15 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -5496,12 +5485,6 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -5514,12 +5497,6 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -5532,12 +5509,6 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -5550,24 +5521,12 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -5580,12 +5539,6 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
@@ -5608,7 +5561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.126.1"
version = "1.127.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.70"
@@ -26,10 +26,6 @@ opt-level = "z"
codegen-units = 1
strip = true
[patch.crates-io]
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
quinn-proto = { git = "https://github.com/quinn-rs/quinn", branch="main" }
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
format-flowed = { path = "./format-flowed" }
@@ -56,7 +52,7 @@ hex = "0.4.0"
hickory-resolver = "0.24"
humansize = "2"
image = { version = "0.24.7", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
iroh = { version = "0.4.1", default-features = false }
iroh = { git = "https://github.com/deltachat/iroh", branch = "0.4-update-quic", default-features = false }
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"

View File

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

View File

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

View File

@@ -55,5 +55,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.126.1"
"version": "1.127.0"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.126.1"
version = "1.127.0"
license = "MPL-2.0"
edition = "2021"

View File

@@ -48,3 +48,7 @@ $ python
'awesome'
>>> rpc.close()
```
## Usage from async code
See the [echobot_async.py](./examples/echobot_async.py) example.

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""
Example asynchronous echo bot
"""
import asyncio
import logging
import sys
from deltachat_rpc_client import EventType, Rpc, SpecialContactId
async def main():
async with Rpc() as rpc:
system_info = await rpc.get_system_info()
logging.info("Running deltachat core %s", system_info["deltachat_core_version"])
account_ids = await rpc.get_all_account_ids()
accid = account_ids[0] if account_ids else await rpc.add_account()
await rpc.set_config(accid, "bot", "1")
if not await rpc.is_configured(accid):
logging.info("Account is not configured, configuring")
await rpc.set_config(accid, "addr", sys.argv[1])
await rpc.set_config(accid, "mail_pw", sys.argv[2])
await rpc.configure(accid)
logging.info("Configured")
else:
logging.info("Account is already configured")
await rpc.start_io(accid)
async def process_messages():
for msgid in await rpc.get_next_msgs(accid):
msg = await rpc.get_message(accid, msgid)
if msg["from_id"] != SpecialContactId.SELF and not msg["is_bot"] and not msg["is_info"]:
await rpc.misc_send_text_message(accid, msg["chat_id"], msg["text"])
await rpc.markseen_msgs(accid, [msgid])
# Process old messages.
await process_messages()
while True:
event = await rpc.wait_for_event(accid)
if event["kind"] == EventType.INFO:
logging.info("%s", event["msg"])
elif event["kind"] == EventType.WARNING:
logging.warning("%s", event["msg"])
elif event["kind"] == EventType.ERROR:
logging.error("%s", event["msg"])
elif event["kind"] == EventType.INCOMING_MSG:
logging.info("Got an incoming message (id=%s)", event["msg_id"])
await process_messages()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())

View File

@@ -1,3 +1,4 @@
import asyncio
import json
import logging
import os
@@ -35,6 +36,10 @@ class Rpc:
self.writer_thread: Thread
self.events_thread: Thread
def get_async_rpc(self) -> "AsyncRpc":
"""Get asynchronous wrapper to use the RPC methods from async code."""
return AsyncRpc(self)
def start(self) -> None:
if sys.version_info >= (3, 11):
self.process = subprocess.Popen(
@@ -84,6 +89,13 @@ class Rpc:
def __exit__(self, _exc_type, _exc, _tb):
self.close()
async def __aenter__(self):
self.__enter__()
return self.get_async_rpc()
async def __aexit__(self, _exc_type, _exc, _tb):
self.__exit__(_exc_type, _exc, _tb)
def reader_loop(self) -> None:
try:
while True:
@@ -165,3 +177,19 @@ class Rpc:
return None
return method
class AsyncRpc:
def __init__(self, sync_rpc: Rpc) -> None:
self._sync_rpc = sync_rpc
def __getattr__(self, attr: str) -> Any:
sync_method = getattr(self._sync_rpc, attr)
if sync_method:
async def method(*args) -> Any:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, sync_method, *args)
return method
return None

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.126.1"
version = "1.127.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -35,6 +35,7 @@ skip = [
{ name = "rand", version = "<0.8" },
{ name = "redox_syscall", version = "0.2.16" },
{ name = "regex-automata", version = "0.1.10" },
{ name = "ring", version = "0.16.20" },
{ name = "regex-syntax", version = "0.6.29" },
{ name = "sec1", version = "0.3.0" },
{ name = "sha2", version = "<0.10" },
@@ -44,15 +45,12 @@ skip = [
{ name = "spki", version = "0.6.0" },
{ name = "syn", version = "1.0.109" },
{ name = "time", version = "<0.3" },
{ name = "untrusted", version = "0.7.1" },
{ name = "wasi", version = "<0.11" },
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
{ name = "windows_aarch64_msvc", version = "<0.48" },
{ name = "windows_i686_gnu", version = "<0.48" },
{ name = "windows_i686_msvc", version = "<0.48" },
{ name = "windows-sys", version = "<0.48" },
{ name = "windows-targets", version = "<0.48" },
{ name = "windows", version = "0.32.0" },
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
{ name = "windows_x86_64_gnu", version = "<0.48" },
{ name = "windows_x86_64_msvc", version = "<0.48" },
]
@@ -86,5 +84,4 @@ license-files = [
github = [
"async-email",
"deltachat",
"quinn-rs",
]

View File

@@ -60,5 +60,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.126.1"
"version": "1.127.0"
}

View File

@@ -1 +1 @@
2023-10-24
2023-10-26

View File

@@ -1,6 +1,7 @@
//! # Account manager module.
use std::collections::BTreeMap;
use std::future::Future;
use std::path::{Path, PathBuf};
use anyhow::{ensure, Context as _, Result};
@@ -155,7 +156,7 @@ impl Accounts {
if let Some(cfg) = self.config.get_account(id) {
let account_path = self.dir.join(cfg.dir);
fs::remove_dir_all(&account_path)
try_many_times(|| fs::remove_dir_all(&account_path))
.await
.context("failed to remove account data")?;
}
@@ -191,10 +192,10 @@ impl Accounts {
fs::create_dir_all(self.dir.join(&account_config.dir))
.await
.context("failed to create dir")?;
fs::rename(&dbfile, &new_dbfile)
try_many_times(|| fs::rename(&dbfile, &new_dbfile))
.await
.context("failed to rename dbfile")?;
fs::rename(&blobdir, &new_blobdir)
try_many_times(|| fs::rename(&blobdir, &new_blobdir))
.await
.context("failed to rename blobdir")?;
if walfile.exists() {
@@ -219,7 +220,7 @@ impl Accounts {
}
Err(err) => {
let account_path = std::path::PathBuf::from(&account_config.dir);
fs::remove_dir_all(&account_path)
try_many_times(|| fs::remove_dir_all(&account_path))
.await
.context("failed to remove account data")?;
self.config.remove_account(account_config.id).await?;
@@ -560,6 +561,37 @@ impl Config {
}
}
/// Spend up to 1 minute trying to do the operation.
///
/// Even if Delta Chat itself does not hold the file lock,
/// there may be other processes such as antivirus,
/// or the filesystem may be network-mounted.
///
/// Without this workaround removing account may fail on Windows with an error
/// "The process cannot access the file because it is being used by another process. (os error 32)".
async fn try_many_times<F, Fut, T>(f: F) -> std::result::Result<(), T>
where
F: Fn() -> Fut,
Fut: Future<Output = std::result::Result<(), T>>,
{
let mut counter = 0;
loop {
counter += 1;
if let Err(err) = f().await {
if counter > 60 {
return Err(err);
}
// Wait 1 second and try again.
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
} else {
break;
}
}
Ok(())
}
/// Configuration of a single account.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct AccountConfig {

View File

@@ -23,7 +23,6 @@ use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
use crate::key::DcKey;
use crate::location;
use crate::log::LogExt;
use crate::message::{
@@ -602,7 +601,11 @@ async fn add_parts(
context,
mime_parser,
is_partial_download.is_some(),
test_normal_chat.is_some() || allow_creation,
if test_normal_chat.is_none() {
allow_creation
} else {
true
},
create_blocked,
from_id,
to_ids,
@@ -614,7 +617,6 @@ async fn add_parts(
}
}
loop {
// if the chat is somehow blocked but we want to create a non-blocked chat,
// unblock the chat
if chat_id_blocked != Blocked::Not && create_blocked != Blocked::Yes {
@@ -703,7 +705,7 @@ async fn add_parts(
}
};
if let Some(chat) = test_normal_chat.as_ref() {
if let Some(chat) = test_normal_chat {
chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
} else if allow_creation {
@@ -717,8 +719,7 @@ async fn add_parts(
}
}
let chat_id_ref = &mut chat_id;
if let Some(chat_id) = *chat_id_ref {
if let Some(chat_id) = chat_id {
if chat_id_blocked != Blocked::Not {
if chat_id_blocked != create_blocked {
chat_id.set_blocked(context, create_blocked).await?;
@@ -765,44 +766,7 @@ async fn add_parts(
// That's why the config is checked here, and not above.
&& context.get_config_bool(Config::VerifiedOneOnOneChats).await?
{
let decryption_info = &mime_parser.decryption_info;
new_protection =
match decryption_info.autocrypt_header.as_ref().filter(|ah| {
Some(&ah.public_key.fingerprint())
!= decryption_info
.peerstate
.as_ref()
.and_then(|p| p.verified_key_fingerprint.as_ref())
}) {
None => {
if let Some(new_chat_id) = create_adhoc_group(
context,
mime_parser,
create_blocked,
&[ContactId::SELF, from_id],
)
.await
.context("could not create ad hoc group")?
{
info!(
context,
"Moving message to a new ad-hoc group keeping 1:1 chat \
protection.",
);
*chat_id_ref = Some(new_chat_id);
chat_id_blocked = create_blocked;
continue;
} else {
warn!(
context,
"Rejected to create an ad-hoc group, but keeping 1:1 chat \
protection.",
);
}
chat.protected
}
Some(_) => ProtectionStatus::ProtectionBroken,
};
new_protection = ProtectionStatus::ProtectionBroken;
}
if chat.protected != new_protection {
// The message itself will be sorted under the device message since the device
@@ -831,8 +795,6 @@ async fn add_parts(
} else {
MessageState::InFresh
};
break;
}
} else {
// Outgoing
@@ -1519,16 +1481,7 @@ async fn lookup_chat_by_reply(
// If this was a private message just to self, it was probably a private reply.
// It should not go into the group then, but into the private chat.
if is_probably_private_reply(
context,
to_ids,
from_id,
mime_parser,
parent_chat.id,
&parent_chat.grpid,
)
.await?
{
if is_probably_private_reply(context, to_ids, from_id, mime_parser, parent_chat.id).await? {
return Ok(None);
}
@@ -1558,14 +1511,13 @@ async fn is_probably_private_reply(
from_id: ContactId,
mime_parser: &MimeMessage,
parent_chat_id: ChatId,
parent_chat_grpid: &str,
) -> Result<bool> {
// Usually we don't want to show private replies in the parent chat, but in the
// 1:1 chat with the sender.
//
// An exception is replies to 2-member groups from classical MUAs or to 2-member ad-hoc
// groups. Such messages can't contain a Chat-Group-Id header and need to be sorted purely by
// References/In-Reply-To.
// There is one exception: Classical MUA replies to two-member groups
// should be assigned to the group chat. We restrict this exception to classical emails, as chat-group-messages
// contain a Chat-Group-Id header and can be sorted into the correct chat this way.
let private_message =
(to_ids == [ContactId::SELF]) || (from_id == ContactId::SELF && to_ids.len() == 1);
@@ -1573,7 +1525,7 @@ async fn is_probably_private_reply(
return Ok(false);
}
if !mime_parser.has_chat_version() || parent_chat_grpid.is_empty() {
if !mime_parser.has_chat_version() {
let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
return Ok(false);
@@ -1608,10 +1560,6 @@ async fn create_or_lookup_group(
member_ids.push(ContactId::SELF);
}
if member_ids.len() < 3 {
info!(context, "Not creating ad-hoc group: too few contacts.");
return Ok(None);
}
let res = create_adhoc_group(context, mime_parser, create_blocked, &member_ids)
.await
.context("could not create ad hoc group")?
@@ -1636,8 +1584,7 @@ async fn create_or_lookup_group(
// they belong to the group because of the Chat-Group-Id or Message-Id header
if let Some(chat_id) = chat_id {
if !mime_parser.has_chat_version()
&& is_probably_private_reply(context, to_ids, from_id, mime_parser, chat_id, &grpid)
.await?
&& is_probably_private_reply(context, to_ids, from_id, mime_parser, chat_id).await?
{
return Ok(None);
}
@@ -2266,6 +2213,11 @@ async fn create_adhoc_group(
return Ok(None);
}
if member_ids.len() < 3 {
info!(context, "Not creating ad-hoc group: too few contacts.");
return Ok(None);
}
// use subject as initial chat name
let grpname = mime_parser
.get_subject()

View File

@@ -4,7 +4,7 @@ use pretty_assertions::assert_eq;
use crate::chat::{Chat, ProtectionStatus};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::constants::{Chattype, DC_GCL_FOR_FORWARDING};
use crate::constants::DC_GCL_FOR_FORWARDING;
use crate::contact::VerifiedStatus;
use crate::contact::{Contact, Origin};
use crate::message::{Message, Viewtype};
@@ -12,9 +12,7 @@ use crate::mimefactory::MimeFactory;
use crate::mimeparser::SystemMessage;
use crate::receive_imf::receive_imf;
use crate::stock_str;
use crate::test_utils::{
get_chat_msg, mark_as_verified, TestContext, TestContextManager,
};
use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager};
use crate::{e2ee, message};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -744,50 +742,6 @@ async fn test_create_oneonone_chat_with_former_verified_contact() -> Result<()>
Ok(())
}
/// Some messages are sent unencrypted, but they mustn't break a verified chat protection.
/// They must go to a new 2-member ad-hoc group instead.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_unencrypted() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice]).await;
mark_as_verified(&alice, &bob).await;
alice.create_chat(&bob).await;
let msg = tcm.send_recv(&bob, &alice, "hi").await;
assert!(!msg.get_showpadlock());
let alice_chat = Chat::load_from_db(&alice, msg.chat_id).await?;
assert_eq!(alice_chat.typ, Chattype::Group);
// The next unencrypted message must get to the same ad-hoc group thanks to "In-Reply-To".
let msg = tcm.send_recv(&bob, &alice, "hi again").await;
assert!(!msg.get_showpadlock());
assert_eq!(msg.chat_id, alice_chat.id);
let alice_chat = Chat::load_from_db(&alice, msg.chat_id).await?;
assert_eq!(alice_chat.typ, Chattype::Group);
// This message is missed by Alice.
let chat_id = bob.get_chat(&alice).await.id;
bob.send_text(chat_id, "hi to the void").await;
// But the next message must get to the same ad-hoc group thanks to "References".
let msg = tcm.send_recv(&bob, &alice, "hi in a new group").await;
assert!(!msg.get_showpadlock());
assert_eq!(msg.chat_id, alice_chat.id);
let alice_chat = Chat::load_from_db(&alice, msg.chat_id).await?;
assert_eq!(alice_chat.typ, Chattype::Group);
let alice_chat = alice.get_chat(&bob).await;
assert!(alice_chat.is_protected());
assert!(!alice_chat.is_protection_broken());
alice
.golden_test_chat(alice_chat.id, "verified_chats_test_unencrypted")
.await;
Ok(())
}
// ============== Helper Functions ==============
async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) {

View File

@@ -20,6 +20,7 @@ use mailparse::headers::Headers;
use mailparse::MailHeaderMap;
use rand::{thread_rng, Rng};
use tokio::{fs, io};
use url::Url;
use crate::chat::{add_device_msg, add_device_msg_with_importance};
use crate::constants::{DC_ELLIPSIS, DC_OUTDATED_WARNING_DAYS};
@@ -481,7 +482,43 @@ pub(crate) fn time() -> i64 {
.as_secs() as i64
}
/// Very simple email address wrapper.
/// Struct containing all mailto information
#[derive(Debug, Default, Eq, PartialEq)]
pub struct MailTo {
pub to: Vec<EmailAddress>,
pub subject: Option<String>,
pub body: Option<String>,
}
/// Parse mailto urls
pub fn parse_mailto(mailto_url: &str) -> Option<MailTo> {
if let Ok(url) = Url::parse(mailto_url) {
if url.scheme() == "mailto" {
let mut mailto: MailTo = Default::default();
// Extract the email address
url.path().split(',').for_each(|email| {
if let Ok(email) = EmailAddress::new(email) {
mailto.to.push(email);
}
});
// Extract query parameters
for (key, value) in url.query_pairs() {
if key == "subject" {
mailto.subject = Some(value.to_string());
} else if key == "body" {
mailto.body = Some(value.to_string());
}
}
Some(mailto)
} else {
None
}
} else {
None
}
}
///
/// Represents an email address, right now just the `name@domain` portion.
///
@@ -1283,4 +1320,55 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
assert_eq!(remove_subject_prefix("Fwd: Subject"), "Subject");
assert_eq!(remove_subject_prefix("Fw: Subject"), "Subject");
}
#[test]
fn test_parse_mailto() {
let mailto_url = "mailto:someone@example.com";
let reps = parse_mailto(mailto_url);
assert_eq!(
Some(MailTo {
to: vec![EmailAddress {
local: "someone".to_string(),
domain: "example.com".to_string()
}],
subject: None,
body: None
}),
reps
);
let mailto_url = "mailto:someone@example.com?subject=Hello%20World";
let reps = parse_mailto(mailto_url);
assert_eq!(
Some(MailTo {
to: vec![EmailAddress {
local: "someone".to_string(),
domain: "example.com".to_string()
}],
subject: Some("Hello World".to_string()),
body: None
}),
reps
);
let mailto_url = "mailto:someone@example.com,someoneelse@example.com?subject=Hello%20World&body=This%20is%20a%20test";
let reps = parse_mailto(mailto_url);
assert_eq!(
Some(MailTo {
to: vec![
EmailAddress {
local: "someone".to_string(),
domain: "example.com".to_string()
},
EmailAddress {
local: "someoneelse".to_string(),
domain: "example.com".to_string()
}
],
subject: Some("Hello World".to_string()),
body: Some("This is a test".to_string())
}),
reps
);
}
}

View File

@@ -1,4 +0,0 @@
Single#Chat#10: bob@example.net [bob@example.net] 🛡️
--------------------------------------------------------------------------------
Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO 🛡️]
--------------------------------------------------------------------------------