mirror of
https://github.com/chatmail/core.git
synced 2026-05-16 21:36:30 +03:00
Compare commits
19 Commits
hpk/per-re
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
219dbd8c42 | ||
|
|
6e70fbf824 | ||
|
|
7a39a0c8ff | ||
|
|
4eda257cfa | ||
|
|
354edb042b | ||
|
|
4bdc3c29ed | ||
|
|
439216c12c | ||
|
|
0bb4c3d073 | ||
|
|
64f65886f6 | ||
|
|
8658f938e5 | ||
|
|
f75b4abefe | ||
|
|
f2f2dd42ed | ||
|
|
8132f32e91 | ||
|
|
3d28951885 | ||
|
|
bc2d624a80 | ||
|
|
fa8fcaaa2b | ||
|
|
31c74d82bd | ||
|
|
6fb2f27831 | ||
|
|
58a09df49a |
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -43,6 +43,7 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-bin: false
|
||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
@@ -96,6 +97,7 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-bin: false
|
||||||
- name: Rustdoc
|
- name: Rustdoc
|
||||||
run: cargo doc --document-private-items --no-deps
|
run: cargo doc --document-private-items --no-deps
|
||||||
|
|
||||||
@@ -141,9 +143,10 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-bin: false
|
||||||
|
|
||||||
- name: Install nextest
|
- name: Install nextest
|
||||||
uses: taiki-e/install-action@5f57d6cb7cd20b14a8a27f522884c4bc8a187458
|
uses: taiki-e/install-action@cca35edeb1d01366c2843b68fc3ca441446d73d3
|
||||||
with:
|
with:
|
||||||
tool: nextest
|
tool: nextest
|
||||||
|
|
||||||
@@ -177,6 +180,7 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-bin: false
|
||||||
|
|
||||||
- name: Build C library
|
- name: Build C library
|
||||||
run: cargo build -p deltachat_ffi
|
run: cargo build -p deltachat_ffi
|
||||||
@@ -205,6 +209,7 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-bin: false
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server
|
- name: Build deltachat-rpc-server
|
||||||
run: cargo build -p deltachat-rpc-server
|
run: cargo build -p deltachat-rpc-server
|
||||||
|
|||||||
14
.github/workflows/deltachat-rpc-server.yml
vendored
14
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server binaries
|
- name: Build deltachat-rpc-server binaries
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server wheels
|
- name: Build deltachat-rpc-server wheels
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server binaries
|
- name: Build deltachat-rpc-server binaries
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
||||||
@@ -106,7 +106,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server wheels
|
- name: Build deltachat-rpc-server wheels
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-wheel
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-wheel
|
||||||
@@ -157,7 +157,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server binaries
|
- name: Build deltachat-rpc-server binaries
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server wheels
|
- name: Build deltachat-rpc-server wheels
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android-wheel
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android-wheel
|
||||||
@@ -208,7 +208,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
|
|
||||||
- name: Download Linux aarch64 binary
|
- name: Download Linux aarch64 binary
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v7
|
||||||
|
|||||||
5
.github/workflows/jsonrpc.yml
vendored
5
.github/workflows/jsonrpc.yml
vendored
@@ -25,7 +25,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- name: Add Rust cache
|
- name: Add Rust cache
|
||||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||||
|
with:
|
||||||
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-bin: false
|
||||||
- name: npm install
|
- name: npm install
|
||||||
working-directory: deltachat-jsonrpc/typescript
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|||||||
6
.github/workflows/nix.yml
vendored
6
.github/workflows/nix.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
- run: nix fmt flake.nix -- --check
|
- run: nix fmt flake.nix -- --check
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
- run: nix build .#${{ matrix.installable }}
|
- run: nix build .#${{ matrix.installable }}
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
@@ -105,5 +105,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
- run: nix build .#${{ matrix.installable }}
|
- run: nix build .#${{ matrix.installable }}
|
||||||
|
|||||||
2
.github/workflows/repl.yml
vendored
2
.github/workflows/repl.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#deltachat-repl-win64
|
run: nix build .#deltachat-repl-win64
|
||||||
- name: Upload binary
|
- name: Upload binary
|
||||||
|
|||||||
4
.github/workflows/upload-docs.yml
vendored
4
.github/workflows/upload-docs.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
- name: Build Python documentation
|
- name: Build Python documentation
|
||||||
run: nix build .#python-docs
|
run: nix build .#python-docs
|
||||||
- name: Upload to py.delta.chat
|
- name: Upload to py.delta.chat
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||||
- uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5
|
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
|
||||||
- name: Build C documentation
|
- name: Build C documentation
|
||||||
run: nix build .#docs
|
run: nix build .#docs
|
||||||
- name: Upload to c.delta.chat
|
- name: Upload to c.delta.chat
|
||||||
|
|||||||
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -2608,6 +2608,25 @@ dependencies = [
|
|||||||
"libm",
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hybrid-array"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hybrid-array"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@@ -3257,6 +3276,16 @@ dependencies = [
|
|||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kem"
|
||||||
|
version = "0.3.0-pre.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b8645470337db67b01a7f966decf7d0bafedbae74147d33e641c67a91df239f"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -3470,6 +3499,35 @@ dependencies = [
|
|||||||
"windows-sys 0.61.1",
|
"windows-sys 0.61.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ml-dsa"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac4a46643af2001eafebcc37031fc459eb72d45057aac5d7a15b00046a2ad6db"
|
||||||
|
dependencies = [
|
||||||
|
"const-oid",
|
||||||
|
"hybrid-array 0.3.1",
|
||||||
|
"num-traits",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"sha3",
|
||||||
|
"signature",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ml-kem"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8de49b3df74c35498c0232031bb7e85f9389f913e2796169c8ab47a53993a18f"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array 0.2.3",
|
||||||
|
"kem",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"sha3",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moka"
|
name = "moka"
|
||||||
version = "0.12.10"
|
version = "0.12.10"
|
||||||
@@ -4205,6 +4263,8 @@ dependencies = [
|
|||||||
"k256",
|
"k256",
|
||||||
"log",
|
"log",
|
||||||
"md-5",
|
"md-5",
|
||||||
|
"ml-dsa",
|
||||||
|
"ml-kem",
|
||||||
"nom 8.0.0",
|
"nom 8.0.0",
|
||||||
"num-bigint-dig",
|
"num-bigint-dig",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@@ -4223,6 +4283,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"sha3",
|
"sha3",
|
||||||
"signature",
|
"signature",
|
||||||
|
"slh-dsa",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"snafu",
|
"snafu",
|
||||||
"twofish",
|
"twofish",
|
||||||
@@ -5687,6 +5748,25 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slh-dsa"
|
||||||
|
version = "0.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd2f20f4049197e03db1104a6452f4d9e96665d79f880198dce4a7026ba5f267"
|
||||||
|
dependencies = [
|
||||||
|
"const-oid",
|
||||||
|
"digest",
|
||||||
|
"hmac",
|
||||||
|
"hybrid-array 0.3.1",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"sha2",
|
||||||
|
"sha3",
|
||||||
|
"signature",
|
||||||
|
"typenum",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
@@ -7425,9 +7505,9 @@ checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.32"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
@@ -7435,9 +7515,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.7.32"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ num-derive = "0.4"
|
|||||||
num-traits = { workspace = true }
|
num-traits = { workspace = true }
|
||||||
parking_lot = "0.12.4"
|
parking_lot = "0.12.4"
|
||||||
percent-encoding = "2.3"
|
percent-encoding = "2.3"
|
||||||
pgp = { version = "0.19.0", default-features = false }
|
pgp = { version = "0.19.0", features = ["draft-pqc"], default-features = false }
|
||||||
pin-project = "1"
|
pin-project = "1"
|
||||||
qrcodegen = "1.7.0"
|
qrcodegen = "1.7.0"
|
||||||
quick-xml = { version = "0.39", features = ["escape-html"] }
|
quick-xml = { version = "0.39", features = ["escape-html"] }
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ pub unsafe extern "C" fn dc_get_config(
|
|||||||
.strdup()
|
.strdup()
|
||||||
} else {
|
} else {
|
||||||
match config::Config::from_str(&key)
|
match config::Config::from_str(&key)
|
||||||
.with_context(|| format!("Invalid key {:?}", &key))
|
.with_context(|| format!("Invalid key {key:?}"))
|
||||||
.log_err(ctx)
|
.log_err(ctx)
|
||||||
{
|
{
|
||||||
Ok(key) => ctx
|
Ok(key) => ctx
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
|||||||
let name_f = entry.file_name();
|
let name_f = entry.file_name();
|
||||||
let name = name_f.to_string_lossy();
|
let name = name_f.to_string_lossy();
|
||||||
if name.ends_with(".eml") {
|
if name.ends_with(".eml") {
|
||||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
let path_plus_name = format!("{real_spec}/{name}");
|
||||||
println!("Import: {path_plus_name}");
|
println!("Import: {path_plus_name}");
|
||||||
if poke_eml_file(context, Path::new(&path_plus_name))
|
if poke_eml_file(context, Path::new(&path_plus_name))
|
||||||
.await
|
.await
|
||||||
@@ -133,11 +133,11 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Import: Cannot open directory \"{}\".", &real_spec);
|
eprintln!("Import: Cannot open directory {real_spec:?}.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
println!("Import: {read_cnt} items read from {real_spec:?}.");
|
||||||
if read_cnt > 0 {
|
if read_cnt > 0 {
|
||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
msg.get_id(),
|
msg.get_id(),
|
||||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||||
if msg.has_location() { "📍" } else { "" },
|
if msg.has_location() { "📍" } else { "" },
|
||||||
&contact_name,
|
contact_name,
|
||||||
contact_id,
|
contact_id,
|
||||||
msgtext,
|
msgtext,
|
||||||
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
||||||
@@ -221,7 +221,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
},
|
},
|
||||||
statestr,
|
statestr,
|
||||||
downloadstate,
|
downloadstate,
|
||||||
&temp2,
|
temp2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +561,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
|
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
|
||||||
summary.text,
|
summary.text,
|
||||||
statestr,
|
statestr,
|
||||||
×tr,
|
timestr,
|
||||||
if chat.is_sending_locations() {
|
if chat.is_sending_locations() {
|
||||||
"📍"
|
"📍"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ async fn handle_cmd(
|
|||||||
{
|
{
|
||||||
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
|
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
|
||||||
} else {
|
} else {
|
||||||
println!("OAuth2 not available for {}.", &addr);
|
println!("OAuth2 not available for {addr}.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("oauth2: set addr first.");
|
println!("oauth2: set addr first.");
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ $ pip install .
|
|||||||
|
|
||||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||||
2. Install tox `pip install -U tox`
|
2. Install tox `pip install -U tox`
|
||||||
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
3. Run `CHATMAIL_DOMAIN=ci-chatmail.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||||
|
|
||||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,12 @@ ignore = [
|
|||||||
# hickory-proto 0.25.2 quadratic complexity issue.
|
# hickory-proto 0.25.2 quadratic complexity issue.
|
||||||
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
||||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0119>
|
# <https://rustsec.org/advisories/RUSTSEC-2026-0119>
|
||||||
"RUSTSEC-2026-0119"
|
"RUSTSEC-2026-0119",
|
||||||
|
|
||||||
|
# Timing side channel in ml-dsa dependency of rPGP.
|
||||||
|
# We enable PQC for encryption rather than signatures.
|
||||||
|
# <https://rustsec.org/advisories/RUSTSEC-2025-0144>
|
||||||
|
"RUSTSEC-2025-0144",
|
||||||
]
|
]
|
||||||
|
|
||||||
[bans]
|
[bans]
|
||||||
@@ -62,6 +67,7 @@ skip = [
|
|||||||
{ name = "getrandom", version = "0.2.12" },
|
{ name = "getrandom", version = "0.2.12" },
|
||||||
{ name = "heck", version = "0.4.1" },
|
{ name = "heck", version = "0.4.1" },
|
||||||
{ name = "http", version = "0.2.12" },
|
{ name = "http", version = "0.2.12" },
|
||||||
|
{ name = "hybrid-array", version = "0.2.3" },
|
||||||
{ name = "linux-raw-sys", version = "0.4.14" },
|
{ name = "linux-raw-sys", version = "0.4.14" },
|
||||||
{ name = "lru", version = "0.12.5" },
|
{ name = "lru", version = "0.12.5" },
|
||||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
{ name = "netlink-packet-route", version = "0.17.1" },
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Run clippy for all Rust code in the project.
|
# Run clippy for all Rust code in the project.
|
||||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
#
|
||||||
|
# To check, run
|
||||||
|
# scripts/clippy.sh
|
||||||
|
#
|
||||||
|
# To automatically fix warnings, run
|
||||||
|
# scripts/clippy.sh --fix --allow-dirty
|
||||||
|
cargo clippy --workspace --all-targets --all-features "$@" -- -D warnings
|
||||||
|
|||||||
@@ -794,7 +794,7 @@ impl Config {
|
|||||||
.with_push_subscriber(push_subscriber.clone())
|
.with_push_subscriber(push_subscriber.clone())
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("failed to create context from file {:?}", &dbfile))?;
|
.with_context(|| format!("failed to create context from file {dbfile:?}"))?;
|
||||||
// Try to open without a passphrase,
|
// Try to open without a passphrase,
|
||||||
// but do not return an error if account is passphare-protected.
|
// but do not return an error if account is passphare-protected.
|
||||||
ctx.open("".to_string()).await?;
|
ctx.open("".to_string()).await?;
|
||||||
|
|||||||
18
src/chat.rs
18
src/chat.rs
@@ -2529,7 +2529,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
|||||||
// running numbers, etc.
|
// running numbers, etc.
|
||||||
let filename: String = match viewtype_orig {
|
let filename: String = match viewtype_orig {
|
||||||
Viewtype::Voice => format!(
|
Viewtype::Voice => format!(
|
||||||
"voice-messsage_{}.{}",
|
"voice-messsage_{}.{suffix}",
|
||||||
chrono::Utc
|
chrono::Utc
|
||||||
.timestamp_opt(msg.timestamp_sort, 0)
|
.timestamp_opt(msg.timestamp_sort, 0)
|
||||||
.single()
|
.single()
|
||||||
@@ -2537,10 +2537,9 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
|||||||
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
||||||
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
|
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
|
||||||
),
|
),
|
||||||
&suffix
|
|
||||||
),
|
),
|
||||||
Viewtype::Image | Viewtype::Gif => format!(
|
Viewtype::Image | Viewtype::Gif => format!(
|
||||||
"image_{}.{}",
|
"image_{}.{suffix}",
|
||||||
chrono::Utc
|
chrono::Utc
|
||||||
.timestamp_opt(msg.timestamp_sort, 0)
|
.timestamp_opt(msg.timestamp_sort, 0)
|
||||||
.single()
|
.single()
|
||||||
@@ -2548,10 +2547,9 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
|||||||
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
||||||
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(),
|
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(),
|
||||||
),
|
),
|
||||||
&suffix,
|
|
||||||
),
|
),
|
||||||
Viewtype::Video => format!(
|
Viewtype::Video => format!(
|
||||||
"video_{}.{}",
|
"video_{}.{suffix}",
|
||||||
chrono::Utc
|
chrono::Utc
|
||||||
.timestamp_opt(msg.timestamp_sort, 0)
|
.timestamp_opt(msg.timestamp_sort, 0)
|
||||||
.single()
|
.single()
|
||||||
@@ -2559,7 +2557,6 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
|
|||||||
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
|| "YY-mm-dd_hh:mm:ss".to_string(),
|
||||||
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
|
|ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
|
||||||
),
|
),
|
||||||
&suffix
|
|
||||||
),
|
),
|
||||||
_ => filename,
|
_ => filename,
|
||||||
};
|
};
|
||||||
@@ -2954,6 +2951,7 @@ WHERE id=?
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let chunk_size = context.get_max_smtp_rcpt_to().await?;
|
||||||
let trans_fn = |t: &mut rusqlite::Transaction| {
|
let trans_fn = |t: &mut rusqlite::Transaction| {
|
||||||
let mut row_ids = Vec::<i64>::new();
|
let mut row_ids = Vec::<i64>::new();
|
||||||
|
|
||||||
@@ -2967,12 +2965,12 @@ WHERE id=?
|
|||||||
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
||||||
VALUES (?1, ?2, ?3, ?4)",
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
)?;
|
)?;
|
||||||
if !recipients.is_empty() {
|
for recipients_chunk in recipients.chunks(chunk_size) {
|
||||||
let all_recipients = recipients.join(" ");
|
let recipients_chunk = recipients_chunk.join(" ");
|
||||||
if let Some(pre_msg) = &rendered_pre_msg {
|
if let Some(pre_msg) = &rendered_pre_msg {
|
||||||
let row_id = stmt.execute((
|
let row_id = stmt.execute((
|
||||||
&pre_msg.rfc724_mid,
|
&pre_msg.rfc724_mid,
|
||||||
&all_recipients,
|
&recipients_chunk,
|
||||||
&pre_msg.message,
|
&pre_msg.message,
|
||||||
msg.id,
|
msg.id,
|
||||||
))?;
|
))?;
|
||||||
@@ -2980,7 +2978,7 @@ WHERE id=?
|
|||||||
}
|
}
|
||||||
let row_id = stmt.execute((
|
let row_id = stmt.execute((
|
||||||
&rendered_msg.rfc724_mid,
|
&rendered_msg.rfc724_mid,
|
||||||
&all_recipients,
|
&recipients_chunk,
|
||||||
&rendered_msg.message,
|
&rendered_msg.message,
|
||||||
msg.id,
|
msg.id,
|
||||||
))?;
|
))?;
|
||||||
|
|||||||
@@ -707,8 +707,7 @@ async fn get_autoconfig(
|
|||||||
ctx,
|
ctx,
|
||||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see <https://releases.mozilla.org/pub/thunderbird/>, which makes some sense
|
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see <https://releases.mozilla.org/pub/thunderbird/>, which makes some sense
|
||||||
&format!(
|
&format!(
|
||||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
"https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
||||||
¶m_domain, ¶m_addr_urlencoded
|
|
||||||
),
|
),
|
||||||
¶m.addr,
|
¶m.addr,
|
||||||
)
|
)
|
||||||
@@ -721,7 +720,7 @@ async fn get_autoconfig(
|
|||||||
// Outlook uses always SSL but different domains (this comment describes the next two steps)
|
// Outlook uses always SSL but different domains (this comment describes the next two steps)
|
||||||
if let Ok(res) = outlk_autodiscover(
|
if let Ok(res) = outlk_autodiscover(
|
||||||
ctx,
|
ctx,
|
||||||
format!("https://{}/autodiscover/autodiscover.xml", ¶m_domain),
|
format!("https://{param_domain}/autodiscover/autodiscover.xml"),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -731,10 +730,7 @@ async fn get_autoconfig(
|
|||||||
|
|
||||||
if let Ok(res) = outlk_autodiscover(
|
if let Ok(res) = outlk_autodiscover(
|
||||||
ctx,
|
ctx,
|
||||||
format!(
|
format!("https://autodiscover.{param_domain}/autodiscover/autodiscover.xml",),
|
||||||
"https://autodiscover.{}/autodiscover/autodiscover.xml",
|
|
||||||
¶m_domain
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -745,7 +741,7 @@ async fn get_autoconfig(
|
|||||||
// always SSL for Thunderbird's database
|
// always SSL for Thunderbird's database
|
||||||
if let Ok(res) = moz_autoconfigure(
|
if let Ok(res) = moz_autoconfigure(
|
||||||
ctx,
|
ctx,
|
||||||
&format!("https://autoconfig.thunderbird.net/v1.1/{}", ¶m_domain),
|
&format!("https://autoconfig.thunderbird.net/v1.1/{param_domain}"),
|
||||||
¶m.addr,
|
¶m.addr,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ pub const MAX_RCVD_IMAGE_PIXELS: u32 = 50_000_000;
|
|||||||
// `max_smtp_rcpt_to` in the provider db.
|
// `max_smtp_rcpt_to` in the provider db.
|
||||||
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
|
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
|
||||||
|
|
||||||
|
/// Same as `DEFAULT_MAX_SMTP_RCPT_TO`, but for chatmail relays.
|
||||||
|
pub(crate) const DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO: usize = 999;
|
||||||
|
|
||||||
/// How far the last quota check needs to be in the past to be checked by the background function (in seconds).
|
/// How far the last quota check needs to be in the past to be checked by the background function (in seconds).
|
||||||
pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: u64 = 12 * 60 * 60; // 12 hours
|
pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: u64 = 12 * 60 * 60; // 12 hours
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use tokio::sync::{Mutex, Notify, RwLock};
|
|||||||
|
|
||||||
use crate::chat::{ChatId, get_chat_cnt};
|
use crate::chat::{ChatId, get_chat_cnt};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
|
use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
|
||||||
use crate::contact::{Contact, ContactId};
|
use crate::contact::{Contact, ContactId};
|
||||||
use crate::debug_logging::DebugLogging;
|
use crate::debug_logging::DebugLogging;
|
||||||
use crate::events::{Event, EventEmitter, EventType, Events};
|
use crate::events::{Event, EventEmitter, EventType, Events};
|
||||||
@@ -587,6 +587,23 @@ impl Context {
|
|||||||
self.get_config_bool(Config::IsChatmail).await
|
self.get_config_bool(Config::IsChatmail).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns maximum number of recipients the provider allows to send a single email to.
|
||||||
|
pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<usize> {
|
||||||
|
let is_chatmail = self.is_chatmail().await?;
|
||||||
|
let val = self
|
||||||
|
.get_configured_provider()
|
||||||
|
.await?
|
||||||
|
.and_then(|provider| provider.opt.max_smtp_rcpt_to)
|
||||||
|
.map_or_else(
|
||||||
|
|| match is_chatmail {
|
||||||
|
true => constants::DEFAULT_CHATMAIL_MAX_SMTP_RCPT_TO,
|
||||||
|
false => constants::DEFAULT_MAX_SMTP_RCPT_TO,
|
||||||
|
},
|
||||||
|
usize::from,
|
||||||
|
);
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
/// Does a single round of fetching from IMAP and returns.
|
/// Does a single round of fetching from IMAP and returns.
|
||||||
///
|
///
|
||||||
/// Can be used even if I/O is currently stopped.
|
/// Can be used even if I/O is currently stopped.
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ mod tests {
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
This message does not have Content-Type nor Subject.<br/>
|
This message does not have Content-Type nor Subject.<br/>
|
||||||
</body></html>
|
</body></html>
|
||||||
"#
|
"#
|
||||||
@@ -322,7 +322,7 @@ This message does not have Content-Type nor Subject.<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
||||||
</body></html>
|
</body></html>
|
||||||
"#
|
"#
|
||||||
@@ -341,7 +341,7 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
This line ends with a space and will be merged with the next one due to format=flowed.<br/>
|
This line ends with a space and will be merged with the next one due to format=flowed.<br/>
|
||||||
<br/>
|
<br/>
|
||||||
This line does not end with a space<br/>
|
This line does not end with a space<br/>
|
||||||
@@ -362,7 +362,7 @@ and will be wrapped as usual.<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
mime-modified should not be set set as there is no html and no special stuff;<br/>
|
mime-modified should not be set set as there is no html and no special stuff;<br/>
|
||||||
although not being a delta-message.<br/>
|
although not being a delta-message.<br/>
|
||||||
test some special html-characters as < > and & but also " and ' :)<br/>
|
test some special html-characters as < > and & but also " and ' :)<br/>
|
||||||
|
|||||||
31
src/imap.rs
31
src/imap.rs
@@ -1045,15 +1045,12 @@ impl Session {
|
|||||||
if target.is_empty() {
|
if target.is_empty() {
|
||||||
self.delete_message_batch(context, &uid_set, rowid_set)
|
self.delete_message_batch(context, &uid_set, rowid_set)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("cannot delete batch of messages {:?}", &uid_set))?;
|
.with_context(|| format!("cannot delete batch of messages {uid_set:?}"))?;
|
||||||
} else {
|
} else {
|
||||||
self.move_message_batch(context, &uid_set, rowid_set, &target)
|
self.move_message_batch(context, &uid_set, rowid_set, &target)
|
||||||
.await
|
.await
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!("cannot move batch of messages {uid_set:?} to folder {target:?}",)
|
||||||
"cannot move batch of messages {:?} to folder {:?}",
|
|
||||||
&uid_set, target
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1287,9 +1284,10 @@ impl Session {
|
|||||||
|
|
||||||
for (request_uids, set) in build_sequence_sets(&request_uids)? {
|
for (request_uids, set) in build_sequence_sets(&request_uids)? {
|
||||||
info!(context, "Starting UID FETCH of message set \"{}\".", set);
|
info!(context, "Starting UID FETCH of message set \"{}\".", set);
|
||||||
let mut fetch_responses = self.uid_fetch(&set, BODY_FULL).await.with_context(|| {
|
let mut fetch_responses = self
|
||||||
format!("fetching messages {} from folder \"{}\"", &set, folder)
|
.uid_fetch(&set, BODY_FULL)
|
||||||
})?;
|
.await
|
||||||
|
.with_context(|| format!("fetching messages {set} from folder {folder:?}"))?;
|
||||||
|
|
||||||
// Map from UIDs to unprocessed FETCH results. We put unprocessed FETCH results here
|
// Map from UIDs to unprocessed FETCH results. We put unprocessed FETCH results here
|
||||||
// when we want to process other messages first.
|
// when we want to process other messages first.
|
||||||
@@ -1503,7 +1501,7 @@ impl Session {
|
|||||||
.get_metadata(
|
.get_metadata(
|
||||||
mailbox,
|
mailbox,
|
||||||
options,
|
options,
|
||||||
"(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn /shared/vendor/deltachat/maxsmtprecipients)",
|
"(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn)",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
for m in metadata {
|
for m in metadata {
|
||||||
@@ -1539,21 +1537,6 @@ impl Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"/shared/vendor/deltachat/maxsmtprecipients" => {
|
|
||||||
if let Some(value) = m.value.and_then(|v| v.parse::<u32>().ok()) {
|
|
||||||
let transport_id = self.transport_id();
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute(
|
|
||||||
"UPDATE transports \
|
|
||||||
SET max_smtp_rcpt_to=? WHERE id=?",
|
|
||||||
(value, transport_id),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err(context)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -427,18 +427,16 @@ mod tests {
|
|||||||
let self_chat = ctx1.get_self_chat().await;
|
let self_chat = ctx1.get_self_chat().await;
|
||||||
let msgs = get_chat_msgs(&ctx1, self_chat.id).await.unwrap();
|
let msgs = get_chat_msgs(&ctx1, self_chat.id).await.unwrap();
|
||||||
assert_eq!(msgs.len(), 2);
|
assert_eq!(msgs.len(), 2);
|
||||||
let msgid = match msgs.first().unwrap() {
|
let ChatItem::Message { msg_id } = msgs.first().unwrap() else {
|
||||||
ChatItem::Message { msg_id } => msg_id,
|
panic!("wrong chat item");
|
||||||
_ => panic!("wrong chat item"),
|
|
||||||
};
|
};
|
||||||
let msg = Message::load_from_db(&ctx1, *msgid).await.unwrap();
|
let msg = Message::load_from_db(&ctx1, *msg_id).await.unwrap();
|
||||||
let text = msg.get_text();
|
let text = msg.get_text();
|
||||||
assert_eq!(text, "hi there");
|
assert_eq!(text, "hi there");
|
||||||
let msgid = match msgs.get(1).unwrap() {
|
let ChatItem::Message { msg_id } = msgs.get(1).unwrap() else {
|
||||||
ChatItem::Message { msg_id } => msg_id,
|
panic!("wrong chat item");
|
||||||
_ => panic!("wrong chat item"),
|
|
||||||
};
|
};
|
||||||
let msg = Message::load_from_db(&ctx1, *msgid).await.unwrap();
|
let msg = Message::load_from_db(&ctx1, *msg_id).await.unwrap();
|
||||||
|
|
||||||
let path = msg.get_file(&ctx1).unwrap();
|
let path = msg.get_file(&ctx1).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
11
src/key.rs
11
src/key.rs
@@ -570,9 +570,11 @@ pub async fn preconfigure_keypair(context: &Context, secret_data: &str) -> Resul
|
|||||||
pub struct Fingerprint(Vec<u8>);
|
pub struct Fingerprint(Vec<u8>);
|
||||||
|
|
||||||
impl Fingerprint {
|
impl Fingerprint {
|
||||||
/// Creates new 160-bit (20 bytes) fingerprint.
|
/// Creates new fingerprint.
|
||||||
|
///
|
||||||
|
/// It is 160-bit (20 bytes) for v4 keys and 32 bytes for v6 keys.
|
||||||
pub fn new(v: Vec<u8>) -> Fingerprint {
|
pub fn new(v: Vec<u8>) -> Fingerprint {
|
||||||
debug_assert_eq!(v.len(), 20);
|
debug_assert!(v.len() == 20 || v.len() == 32);
|
||||||
Fingerprint(v)
|
Fingerprint(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +627,10 @@ impl std::str::FromStr for Fingerprint {
|
|||||||
.filter(|&c| c.is_ascii_hexdigit())
|
.filter(|&c| c.is_ascii_hexdigit())
|
||||||
.collect();
|
.collect();
|
||||||
let v: Vec<u8> = hex::decode(&hex_repr)?;
|
let v: Vec<u8> = hex::decode(&hex_repr)?;
|
||||||
ensure!(v.len() == 20, "wrong fingerprint length: {hex_repr}");
|
ensure!(
|
||||||
|
v.len() == 20 || v.len() == 32,
|
||||||
|
"wrong fingerprint length: {hex_repr}"
|
||||||
|
);
|
||||||
let fp = Fingerprint::new(v);
|
let fp = Fingerprint::new(v);
|
||||||
Ok(fp)
|
Ok(fp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
|||||||
} else {
|
} else {
|
||||||
msg.timestamp_sort
|
msg.timestamp_sort
|
||||||
});
|
});
|
||||||
ret += &format!("Received: {}", &s);
|
ret += &format!("Received: {s}");
|
||||||
ret += "\n";
|
ret += "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
|
|||||||
ret += "Type: ";
|
ret += "Type: ";
|
||||||
ret += &format!("{}", msg.viewtype);
|
ret += &format!("{}", msg.viewtype);
|
||||||
ret += "\n";
|
ret += "\n";
|
||||||
ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
|
ret += &format!("Mimetype: {}\n", msg.get_filemime().unwrap_or_default());
|
||||||
}
|
}
|
||||||
let w = msg.param.get_int(Param::Width).unwrap_or_default();
|
let w = msg.param.get_int(Param::Width).unwrap_or_default();
|
||||||
let h = msg.param.get_int(Param::Height).unwrap_or_default();
|
let h = msg.param.get_int(Param::Height).unwrap_or_default();
|
||||||
|
|||||||
@@ -1939,32 +1939,13 @@ pub(crate) fn render_outer_message(
|
|||||||
/// Takes the encrypted part, wraps it in a MimePart,
|
/// Takes the encrypted part, wraps it in a MimePart,
|
||||||
/// and sets the appropriate Content-Type for the outer message
|
/// and sets the appropriate Content-Type for the outer message
|
||||||
pub(crate) fn wrap_encrypted_part(encrypted: String) -> MimePart<'static> {
|
pub(crate) fn wrap_encrypted_part(encrypted: String) -> MimePart<'static> {
|
||||||
// XXX: additional newline is needed
|
|
||||||
// to pass filtermail at
|
|
||||||
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>:
|
|
||||||
let encrypted = encrypted + "\n";
|
|
||||||
|
|
||||||
MimePart::new(
|
MimePart::new(
|
||||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\"",
|
"multipart/encrypted; protocol=\"application/pgp-encrypted\"",
|
||||||
vec![
|
vec![
|
||||||
// Autocrypt part 1
|
// Autocrypt part 1
|
||||||
MimePart::new("application/pgp-encrypted", "Version: 1\r\n").header(
|
MimePart::new("application/pgp-encrypted", "Version: 1\r\n"),
|
||||||
"Content-Description",
|
|
||||||
mail_builder::headers::raw::Raw::new("PGP/MIME version identification"),
|
|
||||||
),
|
|
||||||
// Autocrypt part 2
|
// Autocrypt part 2
|
||||||
MimePart::new(
|
MimePart::new("application/octet-stream", encrypted),
|
||||||
"application/octet-stream; name=\"encrypted.asc\"",
|
|
||||||
encrypted,
|
|
||||||
)
|
|
||||||
.header(
|
|
||||||
"Content-Description",
|
|
||||||
mail_builder::headers::raw::Raw::new("OpenPGP encrypted message"),
|
|
||||||
)
|
|
||||||
.header(
|
|
||||||
"Content-Disposition",
|
|
||||||
mail_builder::headers::raw::Raw::new("inline; filename=\"encrypted.asc\";"),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ impl MimeMessage {
|
|||||||
let decrypted_msg; // Decrypted signed OpenPGP message.
|
let decrypted_msg; // Decrypted signed OpenPGP message.
|
||||||
let expected_sender_fingerprint: Option<String>;
|
let expected_sender_fingerprint: Option<String>;
|
||||||
|
|
||||||
let (mail, is_encrypted) = match decrypt::decrypt(context, &mail).await {
|
let (mail, is_encrypted) = match Box::pin(decrypt::decrypt(context, &mail)).await {
|
||||||
Ok(Some((mut msg, expected_sender_fp))) => {
|
Ok(Some((mut msg, expected_sender_fp))) => {
|
||||||
mail_raw = msg.as_data_vec().unwrap_or_default();
|
mail_raw = msg.as_data_vec().unwrap_or_default();
|
||||||
|
|
||||||
|
|||||||
16
src/net.rs
16
src/net.rs
@@ -109,8 +109,8 @@ pub(crate) async fn connect_tcp_inner(
|
|||||||
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
|
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
|
||||||
let tcp_stream = timeout(TIMEOUT, TcpStream::connect(addr))
|
let tcp_stream = timeout(TIMEOUT, TcpStream::connect(addr))
|
||||||
.await
|
.await
|
||||||
.context("Connection timeout")?
|
.with_context(|| format!("Connection to {addr} timed out"))?
|
||||||
.context("Connection failure")?;
|
.with_context(|| format!("Connection to {addr} failed"))?;
|
||||||
|
|
||||||
// Disable Nagle's algorithm.
|
// Disable Nagle's algorithm.
|
||||||
tcp_stream.set_nodelay(true)?;
|
tcp_stream.set_nodelay(true)?;
|
||||||
@@ -180,7 +180,7 @@ where
|
|||||||
delay_set.spawn(tokio::time::sleep(delay));
|
delay_set.spawn(tokio::time::sleep(delay));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut first_error = None;
|
let mut all_errors = Vec::new();
|
||||||
|
|
||||||
let res = loop {
|
let res = loop {
|
||||||
if let Some(fut) = futures.next() {
|
if let Some(fut) = futures.next() {
|
||||||
@@ -200,7 +200,7 @@ where
|
|||||||
}
|
}
|
||||||
Ok(Err(err)) => {
|
Ok(Err(err)) => {
|
||||||
// Some connection attempt failed.
|
// Some connection attempt failed.
|
||||||
first_error.get_or_insert(err);
|
all_errors.push(err);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
break Err(err);
|
break Err(err);
|
||||||
@@ -211,9 +211,11 @@ where
|
|||||||
// Out of connection attempts.
|
// Out of connection attempts.
|
||||||
//
|
//
|
||||||
// Break out of the loop and return error.
|
// Break out of the loop and return error.
|
||||||
break Err(
|
break if all_errors.is_empty() {
|
||||||
first_error.unwrap_or_else(|| format_err!("No connection attempts were made"))
|
Err(format_err!("No connection attempts were made"))
|
||||||
);
|
} else {
|
||||||
|
Err(format_err!("All connection attempts failed: {}", all_errors.into_iter().map(|err| format!("{err:#}")).collect::<Vec<String>>().join("; ")))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
37
src/pgp.rs
37
src/pgp.rs
@@ -847,4 +847,41 @@ mod tests {
|
|||||||
assert!(merge_openpgp_certificates(alice.clone(), bob.clone()).is_err());
|
assert!(merge_openpgp_certificates(alice.clone(), bob.clone()).is_err());
|
||||||
assert!(merge_openpgp_certificates(bob.clone(), alice.clone()).is_err());
|
assert!(merge_openpgp_certificates(bob.clone(), alice.clone()).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test PQC support.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_pqc() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let pqc = &tcm.pqc().await;
|
||||||
|
|
||||||
|
let pqc_received_message = tcm.send_recv_accept(alice, pqc, "Hi!").await;
|
||||||
|
let pqc_chat_id = pqc_received_message.chat_id;
|
||||||
|
let pqc_sent = pqc.send_text(pqc_chat_id, "Hello back!").await;
|
||||||
|
|
||||||
|
let alice_rcvd = alice.recv_msg(&pqc_sent).await;
|
||||||
|
assert_eq!(alice_rcvd.text, "Hello back!");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests securejoin with inviter using PQC key.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_securejoin_pqc_inviter() {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let pqc = &tcm.pqc().await;
|
||||||
|
|
||||||
|
tcm.execute_securejoin(pqc, alice).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests securejoin with joiner using PQC key.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_securejoin_pqc_joiner() {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let pqc = &tcm.pqc().await;
|
||||||
|
let bob = &tcm.bob().await;
|
||||||
|
|
||||||
|
tcm.execute_securejoin(bob, pqc).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl PlainText {
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
"#
|
"#
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ http://link-at-start-of-line.org
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
line 1<br/>
|
line 1<br/>
|
||||||
line 2<br/>
|
line 2<br/>
|
||||||
line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a> and <a href="http://link-end-of-line.com/file?foo=bar%20">http://link-end-of-line.com/file?foo=bar%20</a><br/>
|
line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a> and <a href="http://link-end-of-line.com/file?foo=bar%20">http://link-end-of-line.com/file?foo=bar%20</a><br/>
|
||||||
@@ -156,7 +156,7 @@ line with <a href="https://link-mid-of-line.org">https://link-mid-of-line.org</a
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
Foo<br/>
|
Foo<br/>
|
||||||
bar<br/>
|
bar<br/>
|
||||||
</body></html>
|
</body></html>
|
||||||
@@ -178,7 +178,7 @@ bar<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.link/?foo=_bar</a>> here!<br/>
|
line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.link/?foo=_bar</a>> here!<br/>
|
||||||
</body></html>
|
</body></html>
|
||||||
"#
|
"#
|
||||||
@@ -199,7 +199,7 @@ line with <<a href="http://encapsulated.link/?foo=_bar">http://encapsulated.l
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
line with nohttp://no.link here<br/>
|
line with nohttp://no.link here<br/>
|
||||||
</body></html>
|
</body></html>
|
||||||
"#
|
"#
|
||||||
@@ -220,7 +220,7 @@ line with nohttp://no.link here<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:another@one.de">another@one.de</a><br/>
|
just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:another@one.de">another@one.de</a><br/>
|
||||||
</body></html>
|
</body></html>
|
||||||
"#
|
"#
|
||||||
@@ -241,7 +241,7 @@ just an address: <a href="mailto:foo@bar.org">foo@bar.org</a> <a href="mailto:an
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
line still line<br/>
|
line still line<br/>
|
||||||
<em>>quote </em><br/>
|
<em>>quote </em><br/>
|
||||||
<em>>still quote</em><br/>
|
<em>>still quote</em><br/>
|
||||||
@@ -265,7 +265,7 @@ line still line<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
linestill line<br/>
|
linestill line<br/>
|
||||||
<em>>quote </em><br/>
|
<em>>quote </em><br/>
|
||||||
<em>>still quote</em><br/>
|
<em>>still quote</em><br/>
|
||||||
@@ -289,7 +289,7 @@ linestill line<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
line <br/>
|
line <br/>
|
||||||
still line<br/>
|
still line<br/>
|
||||||
<em>>quote </em><br/>
|
<em>>quote </em><br/>
|
||||||
@@ -314,7 +314,7 @@ still line<br/>
|
|||||||
<html><head>
|
<html><head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
</head><body>
|
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||||
def foo():<br/>
|
def foo():<br/>
|
||||||
pass<br/>
|
pass<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ fn inner_generate_secure_join_qr_code(
|
|||||||
d.attr("cx", logo_position_x + HALF_LOGO_SIZE)?;
|
d.attr("cx", logo_position_x + HALF_LOGO_SIZE)?;
|
||||||
d.attr("cy", logo_position_y + HALF_LOGO_SIZE)?;
|
d.attr("cy", logo_position_y + HALF_LOGO_SIZE)?;
|
||||||
d.attr("r", HALF_LOGO_SIZE)?;
|
d.attr("r", HALF_LOGO_SIZE)?;
|
||||||
d.attr("style", format!("fill:{}", &color))
|
d.attr("style", format!("fill:{color}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let avatar_font_size = LOGO_SIZE * 0.65;
|
let avatar_font_size = LOGO_SIZE * 0.65;
|
||||||
|
|||||||
@@ -2344,8 +2344,6 @@ INSERT INTO msgs
|
|||||||
|
|
||||||
/// Checks for "Chat-Edit" and "Chat-Delete" headers,
|
/// Checks for "Chat-Edit" and "Chat-Delete" headers,
|
||||||
/// and edits/deletes existing messages accordingly.
|
/// and edits/deletes existing messages accordingly.
|
||||||
///
|
|
||||||
/// Returns `true` if this message is an edit/deletion request.
|
|
||||||
async fn handle_edit_delete(
|
async fn handle_edit_delete(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mime_parser: &MimeMessage,
|
mime_parser: &MimeMessage,
|
||||||
@@ -3560,12 +3558,7 @@ async fn create_or_lookup_mailinglist_or_broadcast(
|
|||||||
mime_parser.timestamp_sent,
|
mime_parser.timestamp_sent,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.with_context(|| {
|
.with_context(|| format!("failed to create mailinglist '{name}' for grpid={listid}",))?;
|
||||||
format!(
|
|
||||||
"failed to create mailinglist '{}' for grpid={}",
|
|
||||||
&name, &listid
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if chattype == Chattype::InBroadcast {
|
if chattype == Chattype::InBroadcast {
|
||||||
chat::add_to_chat_contacts_table(
|
chat::add_to_chat_contacts_table(
|
||||||
|
|||||||
@@ -704,10 +704,10 @@ async fn test_parse_ndn_group_msg() -> Result<()> {
|
|||||||
assert_eq!(msg.state, MessageState::OutFailed);
|
assert_eq!(msg.state, MessageState::OutFailed);
|
||||||
|
|
||||||
let msgs = chat::get_chat_msgs(&t, msg.chat_id).await?;
|
let msgs = chat::get_chat_msgs(&t, msg.chat_id).await?;
|
||||||
let ChatItem::Message { msg_id } = *msgs.last().unwrap() else {
|
assert!(matches!(
|
||||||
panic!("Wrong item type");
|
*msgs.last().unwrap(),
|
||||||
};
|
ChatItem::Message { msg_id } if msg_id == msg.id
|
||||||
assert_eq!(msg_id, msg.id);
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1598,9 +1598,7 @@ async fn test_in_reply_to() {
|
|||||||
|
|
||||||
// Load the first message from the same chat.
|
// Load the first message from the same chat.
|
||||||
let msgs = chat::get_chat_msgs(&t, msg.chat_id).await.unwrap();
|
let msgs = chat::get_chat_msgs(&t, msg.chat_id).await.unwrap();
|
||||||
let msg_id = if let ChatItem::Message { msg_id } = msgs.first().unwrap() {
|
let ChatItem::Message { msg_id } = msgs.first().unwrap() else {
|
||||||
msg_id
|
|
||||||
} else {
|
|
||||||
panic!("Wrong item type");
|
panic!("Wrong item type");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ pub(crate) fn maybe_network_lost(context: &Context, stores: Vec<ConnectivityStor
|
|||||||
impl fmt::Debug for ConnectivityStore {
|
impl fmt::Debug for ConnectivityStore {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(guard) = self.0.try_lock() {
|
if let Some(guard) = self.0.try_lock() {
|
||||||
write!(f, "ConnectivityStore {:?}", &*guard)
|
write!(f, "ConnectivityStore {:?}", *guard)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "ConnectivityStore [LOCKED]")
|
write!(f, "ConnectivityStore [LOCKED]")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,7 @@ fn shorten_name(name: &str, length: usize) -> String {
|
|||||||
// We use _ rather than ... to avoid dots at the end of the URL, which would confuse linkifiers
|
// We use _ rather than ... to avoid dots at the end of the URL, which would confuse linkifiers
|
||||||
format!(
|
format!(
|
||||||
"{}_",
|
"{}_",
|
||||||
&name
|
name.chars()
|
||||||
.chars()
|
|
||||||
.take(length.saturating_sub(1))
|
.take(length.saturating_sub(1))
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -992,7 +992,7 @@ async fn test_wrong_auth_token() -> Result<()> {
|
|||||||
tcm.send_recv(alice, bob, "hi").await;
|
tcm.send_recv(alice, bob, "hi").await;
|
||||||
|
|
||||||
let alice_qr = get_securejoin_qr(alice, None).await?;
|
let alice_qr = get_securejoin_qr(alice, None).await?;
|
||||||
println!("{}", &alice_qr);
|
println!("{alice_qr}");
|
||||||
let invalid_alice_qr = alice_qr.replace("&s=", "&s=INVALIDAUTHTOKEN&someotherkey=");
|
let invalid_alice_qr = alice_qr.replace("&s=", "&s=INVALIDAUTHTOKEN&someotherkey=");
|
||||||
|
|
||||||
join_securejoin(bob, &invalid_alice_qr).await?;
|
join_securejoin(bob, &invalid_alice_qr).await?;
|
||||||
|
|||||||
147
src/smtp.rs
147
src/smtp.rs
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
mod connect;
|
mod connect;
|
||||||
pub mod send;
|
pub mod send;
|
||||||
#[cfg(test)]
|
|
||||||
mod chunking_tests;
|
|
||||||
|
|
||||||
use anyhow::{Context as _, Error, Result, bail, format_err};
|
use anyhow::{Context as _, Error, Result, bail, format_err};
|
||||||
use async_smtp::response::{Category, Code, Detail};
|
use async_smtp::response::{Category, Code, Detail};
|
||||||
@@ -12,7 +10,6 @@ use tokio::task;
|
|||||||
|
|
||||||
use crate::chat::{ChatId, add_info_msg_with_cmd};
|
use crate::chat::{ChatId, add_info_msg_with_cmd};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants;
|
|
||||||
use crate::contact::{Contact, ContactId};
|
use crate::contact::{Contact, ContactId};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
@@ -37,9 +34,6 @@ pub(crate) struct Smtp {
|
|||||||
/// Email address we are sending from.
|
/// Email address we are sending from.
|
||||||
from: Option<EmailAddress>,
|
from: Option<EmailAddress>,
|
||||||
|
|
||||||
/// Transport used for the current connection.
|
|
||||||
transport_id: Option<u32>,
|
|
||||||
|
|
||||||
/// Timestamp of last successful send/receive network interaction
|
/// Timestamp of last successful send/receive network interaction
|
||||||
/// (eg connect or send succeeded). On initialization and disconnect
|
/// (eg connect or send succeeded). On initialization and disconnect
|
||||||
/// it is set to None.
|
/// it is set to None.
|
||||||
@@ -66,7 +60,6 @@ impl Smtp {
|
|||||||
task::spawn(async move { transport.quit().await });
|
task::spawn(async move { transport.quit().await });
|
||||||
}
|
}
|
||||||
self.last_success = None;
|
self.last_success = None;
|
||||||
self.transport_id = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if smtp was connected but is not known to
|
/// Return true if smtp was connected but is not known to
|
||||||
@@ -96,10 +89,9 @@ impl Smtp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.connectivity.set_connecting(context);
|
self.connectivity.set_connecting(context);
|
||||||
let (transport_id, lp) = ConfiguredLoginParam::load(context)
|
let (_transport_id, lp) = ConfiguredLoginParam::load(context)
|
||||||
.await?
|
.await?
|
||||||
.context("Not configured")?;
|
.context("Not configured")?;
|
||||||
self.transport_id = Some(transport_id);
|
|
||||||
let proxy_config = ProxyConfig::load(context).await?;
|
let proxy_config = ProxyConfig::load(context).await?;
|
||||||
self.connect(
|
self.connect(
|
||||||
context,
|
context,
|
||||||
@@ -173,7 +165,6 @@ impl Smtp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum SendResult {
|
pub(crate) enum SendResult {
|
||||||
/// Message was sent successfully.
|
/// Message was sent successfully.
|
||||||
Success,
|
Success,
|
||||||
@@ -185,36 +176,13 @@ pub(crate) enum SendResult {
|
|||||||
Retry,
|
Retry,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait SmtpSender: Send {
|
|
||||||
fn send_chunk<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
context: &'a Context,
|
|
||||||
recipients: &'a [async_smtp::EmailAddress],
|
|
||||||
body: &'a str,
|
|
||||||
) -> futures::future::BoxFuture<'a, SendResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RealSmtpSender<'a> {
|
|
||||||
smtp: &'a mut Smtp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SmtpSender for RealSmtpSender<'_> {
|
|
||||||
fn send_chunk<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
context: &'a Context,
|
|
||||||
recipients: &'a [async_smtp::EmailAddress],
|
|
||||||
body: &'a str,
|
|
||||||
) -> futures::future::BoxFuture<'a, SendResult> {
|
|
||||||
Box::pin(smtp_send(context, recipients, body, self.smtp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to send a message.
|
/// Tries to send a message.
|
||||||
pub(crate) async fn smtp_send(
|
pub(crate) async fn smtp_send(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
recipients: &[async_smtp::EmailAddress],
|
recipients: &[async_smtp::EmailAddress],
|
||||||
message: &str,
|
message: &str,
|
||||||
smtp: &mut Smtp,
|
smtp: &mut Smtp,
|
||||||
|
msg_id: Option<MsgId>,
|
||||||
) -> SendResult {
|
) -> SendResult {
|
||||||
if recipients.is_empty() {
|
if recipients.is_empty() {
|
||||||
return SendResult::Success;
|
return SendResult::Success;
|
||||||
@@ -342,6 +310,25 @@ pub(crate) async fn smtp_send(
|
|||||||
Ok(()) => SendResult::Success,
|
Ok(()) => SendResult::Success,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let SendResult::Failure(err) = &status
|
||||||
|
&& let Some(msg_id) = msg_id
|
||||||
|
{
|
||||||
|
// We couldn't send the message, so mark it as failed
|
||||||
|
match Message::load_from_db(context, msg_id).await {
|
||||||
|
Ok(mut msg) => {
|
||||||
|
if let Err(err) = message::set_msg_failed(context, &mut msg, &err.to_string()).await
|
||||||
|
{
|
||||||
|
error!(context, "Failed to mark {msg_id} as failed: {err:#}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
context,
|
||||||
|
"Failed to load {msg_id} to mark it as failed: {err:#}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,40 +406,7 @@ pub(crate) async fn send_msg_to_smtp(
|
|||||||
)
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let transport_id = smtp
|
let status = smtp_send(context, &recipients_list, body.as_str(), smtp, Some(msg_id)).await;
|
||||||
.transport_id
|
|
||||||
.context("SMTP not connected to a transport")?;
|
|
||||||
let chunk_size = max_smtp_rcpt_to(context, transport_id).await?;
|
|
||||||
|
|
||||||
let mut sender = RealSmtpSender { smtp };
|
|
||||||
let (status, start_idx) = send_smtp_chunks(
|
|
||||||
context,
|
|
||||||
&recipients_list,
|
|
||||||
body.as_str(),
|
|
||||||
chunk_size,
|
|
||||||
&mut sender,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let unsent_saved = start_idx < recipients_list.len();
|
|
||||||
if let Some(unsent) = recipients_list.get(start_idx..)
|
|
||||||
&& !unsent.is_empty()
|
|
||||||
{
|
|
||||||
let unsent_str: String = unsent
|
|
||||||
.iter()
|
|
||||||
.map(|a| a.as_ref())
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(" ");
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute(
|
|
||||||
"UPDATE smtp SET recipients=? WHERE id=?",
|
|
||||||
(unsent_str, rowid),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err(context)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
SendResult::Retry => {}
|
SendResult::Retry => {}
|
||||||
@@ -501,16 +455,11 @@ pub(crate) async fn send_msg_to_smtp(
|
|||||||
.await?;
|
.await?;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if let Some(mut msg) = Message::load_from_db_optional(context, msg_id).await? {
|
|
||||||
message::set_msg_failed(context, &mut msg, &err.to_string()).await?;
|
|
||||||
}
|
|
||||||
if !unsent_saved {
|
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
|
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
@@ -521,38 +470,9 @@ pub(crate) async fn send_msg_to_smtp(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
SendResult::Failure(err) => {
|
SendResult::Failure(err) => Err(format_err!("{err}")),
|
||||||
if unsent_saved {
|
|
||||||
Err(format_err!("Retry"))
|
|
||||||
} else {
|
|
||||||
Err(format_err!("{err}"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn max_smtp_rcpt_to(context: &Context, transport_id: u32) -> Result<usize> {
|
|
||||||
let limit = context
|
|
||||||
.sql
|
|
||||||
.query_row_optional(
|
|
||||||
"SELECT max_smtp_rcpt_to FROM transports WHERE id=?",
|
|
||||||
(transport_id,),
|
|
||||||
|row| row.get::<_, u32>(0),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
if limit > 0 {
|
|
||||||
return Ok(limit as usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
let val = context
|
|
||||||
.get_configured_provider()
|
|
||||||
.await?
|
|
||||||
.and_then(|provider| provider.opt.max_smtp_rcpt_to)
|
|
||||||
.map_or(constants::DEFAULT_MAX_SMTP_RCPT_TO, usize::from);
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn msg_has_pending_smtp_job(
|
pub(crate) async fn msg_has_pending_smtp_job(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
@@ -680,7 +600,7 @@ async fn send_mdn_rfc724_mid(
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match smtp_send(context, &recipients, &body, smtp).await {
|
match smtp_send(context, &recipients, &body, smtp, None).await {
|
||||||
SendResult::Success => {
|
SendResult::Success => {
|
||||||
if !recipients.is_empty() {
|
if !recipients.is_empty() {
|
||||||
info!(context, "Successfully sent MDN for {rfc724_mid}.");
|
info!(context, "Successfully sent MDN for {rfc724_mid}.");
|
||||||
@@ -802,22 +722,3 @@ pub(crate) async fn add_self_recipients(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::arithmetic_side_effects)]
|
|
||||||
pub(crate) async fn send_smtp_chunks(
|
|
||||||
context: &Context,
|
|
||||||
recipients: &[async_smtp::EmailAddress],
|
|
||||||
body: &str,
|
|
||||||
chunk_size: usize,
|
|
||||||
sender: &mut (dyn SmtpSender + Send),
|
|
||||||
) -> (SendResult, usize) {
|
|
||||||
for (i, chunk) in recipients.chunks(chunk_size).enumerate() {
|
|
||||||
let status = sender.send_chunk(context, chunk, body).await;
|
|
||||||
match status {
|
|
||||||
SendResult::Success => continue,
|
|
||||||
SendResult::Failure(_) => return (status, (i + 1) * chunk_size),
|
|
||||||
SendResult::Retry => return (status, i * chunk_size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(SendResult::Success, recipients.len())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
use crate::smtp::{send_smtp_chunks, SendResult, SmtpSender};
|
|
||||||
use crate::test_utils::TestContextManager;
|
|
||||||
use crate::context::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use futures::future::{BoxFuture, FutureExt};
|
|
||||||
|
|
||||||
/// Result the mock should return on the designated call.
|
|
||||||
enum MockFailure {
|
|
||||||
Transient,
|
|
||||||
Permanent,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MockSmtpSender {
|
|
||||||
call_count: usize,
|
|
||||||
fail_on_call: Option<(usize, MockFailure)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SmtpSender for MockSmtpSender {
|
|
||||||
fn send_chunk<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
_context: &'a Context,
|
|
||||||
_recipients: &'a [async_smtp::EmailAddress],
|
|
||||||
_body: &'a str,
|
|
||||||
) -> BoxFuture<'a, SendResult> {
|
|
||||||
self.call_count += 1;
|
|
||||||
let count = self.call_count;
|
|
||||||
let fail_on = self.fail_on_call.as_ref().map(|(n, _)| *n);
|
|
||||||
let is_permanent = matches!(
|
|
||||||
self.fail_on_call,
|
|
||||||
Some((_, MockFailure::Permanent))
|
|
||||||
);
|
|
||||||
async move {
|
|
||||||
if fail_on == Some(count) {
|
|
||||||
if is_permanent {
|
|
||||||
return SendResult::Failure(
|
|
||||||
anyhow::format_err!("permanent error"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SendResult::Retry;
|
|
||||||
}
|
|
||||||
SendResult::Success
|
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
||||||
async fn test_send_smtp_chunks() -> Result<()> {
|
|
||||||
let mut tcm = TestContextManager::new();
|
|
||||||
let alice = tcm.alice().await;
|
|
||||||
|
|
||||||
let recipients: Vec<_> = ["r1@ex.org", "r2@ex.org", "r3@ex.org", "r4@ex.org", "r5@ex.org"]
|
|
||||||
.iter()
|
|
||||||
.map(|a| async_smtp::EmailAddress::new(a.to_string()).unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// All chunks succeed.
|
|
||||||
let mut sender = MockSmtpSender { call_count: 0, fail_on_call: None };
|
|
||||||
let (status, processed) =
|
|
||||||
send_smtp_chunks(&alice.ctx, &recipients, "body", 2, &mut sender).await;
|
|
||||||
assert!(matches!(status, SendResult::Success));
|
|
||||||
assert_eq!(processed, 5);
|
|
||||||
assert_eq!(sender.call_count, 3); // chunks: [2, 2, 1]
|
|
||||||
|
|
||||||
// Second chunk gets a transient error, only first chunk's recipients are processed.
|
|
||||||
let mut sender =
|
|
||||||
MockSmtpSender { call_count: 0, fail_on_call: Some((2, MockFailure::Transient)) };
|
|
||||||
let (status, processed) =
|
|
||||||
send_smtp_chunks(&alice.ctx, &recipients, "body", 2, &mut sender).await;
|
|
||||||
assert!(matches!(status, SendResult::Retry));
|
|
||||||
assert_eq!(processed, 2);
|
|
||||||
assert_eq!(sender.call_count, 2);
|
|
||||||
|
|
||||||
// Last chunk gets a transient error, first two chunks' recipients are processed.
|
|
||||||
let mut sender =
|
|
||||||
MockSmtpSender { call_count: 0, fail_on_call: Some((3, MockFailure::Transient)) };
|
|
||||||
let (status, processed) =
|
|
||||||
send_smtp_chunks(&alice.ctx, &recipients, "body", 2, &mut sender).await;
|
|
||||||
assert!(matches!(status, SendResult::Retry));
|
|
||||||
assert_eq!(processed, 4);
|
|
||||||
assert_eq!(sender.call_count, 3);
|
|
||||||
|
|
||||||
// Second chunk gets a permanent error; processed includes the failed chunk.
|
|
||||||
let mut sender =
|
|
||||||
MockSmtpSender { call_count: 0, fail_on_call: Some((2, MockFailure::Permanent)) };
|
|
||||||
let (status, processed) =
|
|
||||||
send_smtp_chunks(&alice.ctx, &recipients, "body", 2, &mut sender).await;
|
|
||||||
assert!(matches!(status, SendResult::Failure(_)));
|
|
||||||
assert_eq!(processed, 4);
|
|
||||||
assert_eq!(sender.call_count, 2);
|
|
||||||
|
|
||||||
// Last chunk gets a permanent error; processed includes the failed chunk.
|
|
||||||
let mut sender =
|
|
||||||
MockSmtpSender { call_count: 0, fail_on_call: Some((3, MockFailure::Permanent)) };
|
|
||||||
let (status, processed) =
|
|
||||||
send_smtp_chunks(&alice.ctx, &recipients, "body", 2, &mut sender).await;
|
|
||||||
assert!(matches!(status, SendResult::Failure(_)));
|
|
||||||
assert_eq!(processed, 6); // capped at (i+1)*chunk_size, may exceed len
|
|
||||||
assert_eq!(sender.call_count, 3);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -2378,22 +2378,13 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
|||||||
sql.execute_migration(
|
sql.execute_migration(
|
||||||
"
|
"
|
||||||
UPDATE msgs SET state=26 WHERE state=28; -- Change OutMdnRcvd to OutDelivered.
|
UPDATE msgs SET state=26 WHERE state=28; -- Change OutMdnRcvd to OutDelivered.
|
||||||
UPDATE msgs SET state=19 WHERE state=24; -- Change OutPreparing to OutFailed.
|
UPDATE msgs SET state=24 WHERE state=18; -- Change OutPreparing to OutFailed.
|
||||||
",
|
",
|
||||||
migration_version,
|
migration_version,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
inc_and_check(&mut migration_version, 153)?;
|
|
||||||
if dbversion < migration_version {
|
|
||||||
sql.execute_migration(
|
|
||||||
"ALTER TABLE transports ADD COLUMN max_smtp_rcpt_to INTEGER NOT NULL DEFAULT 0",
|
|
||||||
migration_version,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_version = sql
|
let new_version = sql
|
||||||
.get_raw_config_int(VERSION_CFG)
|
.get_raw_config_int(VERSION_CFG)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -137,6 +137,17 @@ impl TestContextManager {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new "device" with a preconfigured v6 PQC key.
|
||||||
|
pub async fn pqc(&mut self) -> TestContext {
|
||||||
|
TestContext::builder()
|
||||||
|
.with_key_pair(pqc_keypair())
|
||||||
|
.with_address("pqc@example.org".to_string())
|
||||||
|
.with_id_offset(7000)
|
||||||
|
.with_log_sink(self.log_sink.clone())
|
||||||
|
.build(Some(&mut self.used_names))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new unconfigured test account.
|
/// Creates a new unconfigured test account.
|
||||||
pub async fn unconfigured(&mut self) -> TestContext {
|
pub async fn unconfigured(&mut self) -> TestContext {
|
||||||
TestContext::builder()
|
TestContext::builder()
|
||||||
@@ -304,6 +315,9 @@ impl TestContextManager {
|
|||||||
pub struct TestContextBuilder {
|
pub struct TestContextBuilder {
|
||||||
key_pair: Option<SignedSecretKey>,
|
key_pair: Option<SignedSecretKey>,
|
||||||
|
|
||||||
|
/// Email address.
|
||||||
|
address: Option<String>,
|
||||||
|
|
||||||
/// Log sink if set.
|
/// Log sink if set.
|
||||||
///
|
///
|
||||||
/// If log sink is not set,
|
/// If log sink is not set,
|
||||||
@@ -328,6 +342,7 @@ impl TestContextBuilder {
|
|||||||
/// This is a shortcut for `.with_key_pair(alice_keypair())`.
|
/// This is a shortcut for `.with_key_pair(alice_keypair())`.
|
||||||
pub fn configure_alice(self) -> Self {
|
pub fn configure_alice(self) -> Self {
|
||||||
self.with_key_pair(alice_keypair())
|
self.with_key_pair(alice_keypair())
|
||||||
|
.with_address("alice@example.org".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures as bob@example.net with fixed secret key.
|
/// Configures as bob@example.net with fixed secret key.
|
||||||
@@ -335,6 +350,7 @@ impl TestContextBuilder {
|
|||||||
/// This is a shortcut for `.with_key_pair(bob_keypair())`.
|
/// This is a shortcut for `.with_key_pair(bob_keypair())`.
|
||||||
pub fn configure_bob(self) -> Self {
|
pub fn configure_bob(self) -> Self {
|
||||||
self.with_key_pair(bob_keypair())
|
self.with_key_pair(bob_keypair())
|
||||||
|
.with_address("bob@example.net".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures as charlie@example.net with fixed secret key.
|
/// Configures as charlie@example.net with fixed secret key.
|
||||||
@@ -342,6 +358,7 @@ impl TestContextBuilder {
|
|||||||
/// This is a shortcut for `.with_key_pair(charlie_keypair())`.
|
/// This is a shortcut for `.with_key_pair(charlie_keypair())`.
|
||||||
pub fn configure_charlie(self) -> Self {
|
pub fn configure_charlie(self) -> Self {
|
||||||
self.with_key_pair(charlie_keypair())
|
self.with_key_pair(charlie_keypair())
|
||||||
|
.with_address("charlie@example.net".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures as dom@example.net with fixed secret key.
|
/// Configures as dom@example.net with fixed secret key.
|
||||||
@@ -349,6 +366,7 @@ impl TestContextBuilder {
|
|||||||
/// This is a shortcut for `.with_key_pair(dom_keypair())`.
|
/// This is a shortcut for `.with_key_pair(dom_keypair())`.
|
||||||
pub fn configure_dom(self) -> Self {
|
pub fn configure_dom(self) -> Self {
|
||||||
self.with_key_pair(dom_keypair())
|
self.with_key_pair(dom_keypair())
|
||||||
|
.with_address("dom@example.net".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures as elena@example.net with fixed secret key.
|
/// Configures as elena@example.net with fixed secret key.
|
||||||
@@ -356,6 +374,7 @@ impl TestContextBuilder {
|
|||||||
/// This is a shortcut for `.with_key_pair(elena_keypair())`.
|
/// This is a shortcut for `.with_key_pair(elena_keypair())`.
|
||||||
pub fn configure_elena(self) -> Self {
|
pub fn configure_elena(self) -> Self {
|
||||||
self.with_key_pair(elena_keypair())
|
self.with_key_pair(elena_keypair())
|
||||||
|
.with_address("elena@example.net".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures as fiona@example.net with fixed secret key.
|
/// Configures as fiona@example.net with fixed secret key.
|
||||||
@@ -363,6 +382,7 @@ impl TestContextBuilder {
|
|||||||
/// This is a shortcut for `.with_key_pair(fiona_keypair())`.
|
/// This is a shortcut for `.with_key_pair(fiona_keypair())`.
|
||||||
pub fn configure_fiona(self) -> Self {
|
pub fn configure_fiona(self) -> Self {
|
||||||
self.with_key_pair(fiona_keypair())
|
self.with_key_pair(fiona_keypair())
|
||||||
|
.with_address("fiona@example.net".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the new [`TestContext`] with the provided [`SignedSecretKey`].
|
/// Configures the new [`TestContext`] with the provided [`SignedSecretKey`].
|
||||||
@@ -374,6 +394,12 @@ impl TestContextBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets email address.
|
||||||
|
pub fn with_address(mut self, address: String) -> Self {
|
||||||
|
self.address = Some(address);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Attaches a [`LogSink`] to this [`TestContext`].
|
/// Attaches a [`LogSink`] to this [`TestContext`].
|
||||||
///
|
///
|
||||||
/// This is useful when using multiple [`TestContext`] instances in one test: it allows
|
/// This is useful when using multiple [`TestContext`] instances in one test: it allows
|
||||||
@@ -396,16 +422,7 @@ impl TestContextBuilder {
|
|||||||
/// Builds the [`TestContext`].
|
/// Builds the [`TestContext`].
|
||||||
pub async fn build(self, used_names: Option<&mut BTreeSet<String>>) -> TestContext {
|
pub async fn build(self, used_names: Option<&mut BTreeSet<String>>) -> TestContext {
|
||||||
if let Some(key_pair) = self.key_pair {
|
if let Some(key_pair) = self.key_pair {
|
||||||
let userid = {
|
let addr = self.address.expect("Address is not set").clone();
|
||||||
let public_key = key_pair.to_public_key();
|
|
||||||
let id_bstr = public_key.details.users.first().unwrap().id.id();
|
|
||||||
String::from_utf8(id_bstr.to_vec()).unwrap()
|
|
||||||
};
|
|
||||||
let addr = mailparse::addrparse(&userid)
|
|
||||||
.unwrap()
|
|
||||||
.extract_single_info()
|
|
||||||
.unwrap()
|
|
||||||
.addr;
|
|
||||||
let name = EmailAddress::new(&addr).unwrap().local;
|
let name = EmailAddress::new(&addr).unwrap().local;
|
||||||
|
|
||||||
let mut unused_name = name.clone();
|
let mut unused_name = name.clone();
|
||||||
@@ -1420,6 +1437,13 @@ pub fn fiona_keypair() -> SignedSecretKey {
|
|||||||
key::SignedSecretKey::from_asc(include_str!("../test-data/key/fiona-secret.asc")).unwrap()
|
key::SignedSecretKey::from_asc(include_str!("../test-data/key/fiona-secret.asc")).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a pre-generated v6 PQC keypair from disk.
|
||||||
|
///
|
||||||
|
/// Like [alice_keypair] but a different key and identity.
|
||||||
|
pub fn pqc_keypair() -> SignedSecretKey {
|
||||||
|
key::SignedSecretKey::from_asc(include_str!("../test-data/key/pqc-secret.asc")).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Utility to help wait for and retrieve events.
|
/// Utility to help wait for and retrieve events.
|
||||||
///
|
///
|
||||||
/// This buffers the events in order they are emitted. This allows consuming events in
|
/// This buffers the events in order they are emitted. This allows consuming events in
|
||||||
@@ -1557,9 +1581,7 @@ pub(crate) async fn get_chat_msg(
|
|||||||
asserted_msgs_count,
|
asserted_msgs_count,
|
||||||
msgs.len()
|
msgs.len()
|
||||||
);
|
);
|
||||||
let msg_id = if let ChatItem::Message { msg_id } = msgs[index] {
|
let ChatItem::Message { msg_id } = msgs[index] else {
|
||||||
msg_id
|
|
||||||
} else {
|
|
||||||
panic!("Wrong item type");
|
panic!("Wrong item type");
|
||||||
};
|
};
|
||||||
Message::load_from_db(&t.ctx, msg_id).await.unwrap()
|
Message::load_from_db(&t.ctx, msg_id).await.unwrap()
|
||||||
@@ -1685,7 +1707,7 @@ async fn write_msg(context: &Context, prefix: &str, msg: &Message, buf: &mut Str
|
|||||||
msg.get_id(),
|
msg.get_id(),
|
||||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||||
if msg.has_location() { "📍" } else { "" },
|
if msg.has_location() { "📍" } else { "" },
|
||||||
&contact_name,
|
contact_name,
|
||||||
contact_id,
|
contact_id,
|
||||||
msgtext,
|
msgtext,
|
||||||
if msg.get_from_id() == ContactId::SELF {
|
if msg.get_from_id() == ContactId::SELF {
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ async fn check_that_transition_worked(
|
|||||||
2,
|
2,
|
||||||
"Group {} has members {:?}, but should have members {:?} and {:?}",
|
"Group {} has members {:?}, but should have members {:?} and {:?}",
|
||||||
group,
|
group,
|
||||||
&members,
|
members,
|
||||||
alice_contact_id,
|
alice_contact_id,
|
||||||
ContactId::SELF
|
ContactId::SELF
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ pub(crate) fn truncate(buf: &str, approx_chars: usize) -> Cow<'_, str> {
|
|||||||
if let Some(index) = buf.get(..end_pos).and_then(|s| s.rfind([' ', '\n'])) {
|
if let Some(index) = buf.get(..end_pos).and_then(|s| s.rfind([' ', '\n'])) {
|
||||||
Cow::Owned(format!(
|
Cow::Owned(format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
&buf.get(..=index).unwrap_or_default(),
|
buf.get(..=index).unwrap_or_default(),
|
||||||
DC_ELLIPSIS
|
DC_ELLIPSIS
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Cow::Owned(format!(
|
Cow::Owned(format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
&buf.get(..end_pos).unwrap_or_default(),
|
buf.get(..end_pos).unwrap_or_default(),
|
||||||
DC_ELLIPSIS
|
DC_ELLIPSIS
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,12 +247,12 @@ proptest! {
|
|||||||
assert!(
|
assert!(
|
||||||
l <= approx_chars + el_len,
|
l <= approx_chars + el_len,
|
||||||
"buf: '{}' - res: '{}' - len {}, approx {}",
|
"buf: '{}' - res: '{}' - len {}, approx {}",
|
||||||
&buf, &res, res.len(), approx_chars
|
buf, res, res.len(), approx_chars
|
||||||
);
|
);
|
||||||
|
|
||||||
if buf.chars().count() > approx_chars + el_len {
|
if buf.chars().count() > approx_chars + el_len {
|
||||||
let l = res.len();
|
let l = res.len();
|
||||||
assert_eq!(&res[l-5..l], "[...]", "missing ellipsis in {}", &res);
|
assert_eq!(&res[l-5..l], "[...]", "missing ellipsis in {res}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ pub(crate) struct ConnectionCandidate {
|
|||||||
|
|
||||||
impl fmt::Display for ConnectionCandidate {
|
impl fmt::Display for ConnectionCandidate {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}:{}:{}", &self.host, self.port, self.security)?;
|
write!(f, "{}:{}:{}", self.host, self.port, self.security)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ pub(crate) struct ConfiguredServerLoginParam {
|
|||||||
|
|
||||||
impl fmt::Display for ConfiguredServerLoginParam {
|
impl fmt::Display for ConfiguredServerLoginParam {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}:{}", self.connection, &self.user)?;
|
write!(f, "{}:{}", self.connection, self.user)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
test-data/key/pqc-secret.asc
Normal file
39
test-data/key/pqc-secret.asc
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
xUsGaf8NSRsAAAAgYy+GaofURMeV0+bcZZGY2ZdAamU+LG69ONjd3haVU3cAhm6G
|
||||||
|
IT/UEgFgVdPEhiXER9cfPLiCgkiw/L5mrAZfuLfCqgYfGwgAAABLBQJp/w1JIiEG
|
||||||
|
hys0q6D+DFWPnwQoWtuX0mL6ovH2kCjWmDufAFmB0+QCGwMCHgkECwkIBwYVCg4J
|
||||||
|
CAwBFg0nCQIIAgcCCQEIAQcBAAAAAEGQEO9Py9Q7njj1WXhtn1wMJSLBdHBE+qQu
|
||||||
|
RaCaiWkY5l4EWLlVRPAjX2bBSGq6n3+M+H6oFpOHETAX8IcFSxc260UD+PM0jQpV
|
||||||
|
H6ReNy7PBCQKx8RrBmn/DUkjAAAEwPmkVcPy1ye0/7D9nDQCkENUGry97iLkpcw/
|
||||||
|
tLJfzL5gJAdzrPkDkyukHxrO7kiUx+mzpiGZRZeyRgBd5YQ+mTgGrptxXLFHcKFR
|
||||||
|
79Fjg1UjgHEFjxCkCHUfnNcGZVM3p5skESnNgzsgFGiODfKhM4ew3AFgkUc5LNZj
|
||||||
|
Zgpgt4ETIhylbLUY89ccfNpKnQeJl3cv8lvA/yqhoUutJXwZQ/qYKFnEIGEBTFto
|
||||||
|
hLZn0KauF9KYOYvOV4yjeZQBlxSPNAWj9SqSNcalpTUFzwoQVSsqWwiys1PEzGAu
|
||||||
|
twQVKsZ3e/hlZAyR4eGMiYEmCEy7qjuaOJsqHQuW7hdOHWdVRUpRHOtfj3QAzdc0
|
||||||
|
CehVbyCRJVwnTSKiT3AYsdACH8U7mhI5/VxeSHNRIDN1Y6g6N5sx6Wur/HuKGFwx
|
||||||
|
L4urdPdpJJgyLXR8GUkL/yeqUhogu4mbVAmULbq2BCIKFNpMyGdhnDugN6Sp5MWc
|
||||||
|
GOxCW7CASuBYPHW/rto0C4M/3gCtN2sPtRAhOsXNBBhMqLlzzCgawulCiGtNjHUK
|
||||||
|
HsVhghgYwKRBT7vLSKDNsCVizzoZxNQq8yUEXpFIRsTGt3wYoigZn4wOSpmQbxGe
|
||||||
|
P3Uc2GWuuukCBNEP5oW4+TCFaNw5mvZgZwl5n4K34poxVgpqBIM2m2fEu8oyLPJZ
|
||||||
|
bBxnbty3MUAdLpxv+0otGSHJF4xa3lsEyUdr6+JZZXohNXKoyjeJMGo6qPkvCADI
|
||||||
|
upMnDSYZeLU5bVstHWS6otuRMEcjdLBkYfqfzBhkzbptscaUXzsaK4cd/iQzAA1r
|
||||||
|
A0ygvcA78Vo363cElNAJh3lntrZZGpBYnzcU/zLACKAVJCYPy3Cj8Al8x+gHP0Yr
|
||||||
|
ZSOYdZA1q9s2Kuqk7upCpcYDZ+uXGZs3ubA0TYCcO3FKhAwLhzJad5WApBFETYt2
|
||||||
|
3KJEwgEjQaCs7sNNiwaKxhLC2VJhUckgluGs5iUu9ck5jdU+N9MqTmloF/u2Gok8
|
||||||
|
QEqF9+DBhPg/fJoI9sN8sIyLrksEUQsm59mvJbVWOpxtbwpWZ+J4cat4azHE0khy
|
||||||
|
rolL6lZqDJYW4xVeoAVl5iccicjE6mJLemoxf6iJdohi5cN5JXyZtgtdsbIesJib
|
||||||
|
BLPJVahmv5W1Q4RmrwEp5Ua4xra5Mcac4PeINTOkGMErIhdvnuxEH/Cxd8VKhNlU
|
||||||
|
vdty4MOyUOkRRPhOMNKUyTkwS9yjprK3QbhEJgrJygHCGpQ0jwp9PrtKqNnOONSX
|
||||||
|
t4ZORuiAYHDFz3DPlJhLLzNoAJse0RAyolkPThoMl2JlY5ci8pVHb+Ed/kaeFxnE
|
||||||
|
UJJIOZvDFfNCFCM5CCXG/2pi/icA7nHPDFVBeYPMz1B5vrgmdDNMMFVQNMBtrroT
|
||||||
|
4pi1N5U4+EAv1vah40akQ/iFcfZjt4sE/jG0M0NCWqHBDPO7e0ae+2IqnIJmsHjL
|
||||||
|
N1ak3egY00CnRHdrPCkOkhooFIHA1hYxIwyP07qfkhUBNwSqZ4AfF8UW/nuLjeaj
|
||||||
|
ajEWz3zGLvQpfHSobEGPQKk+eIA1fOVeAuJAUAmJz5YO1Dk4OfczeQqQiOhtv+qe
|
||||||
|
PYaZQfBFJVamGocDHomPQkP/IvAJhuO9xWPapqbdRwGfVRJZgGsAy89mT1w0PU1C
|
||||||
|
u6VpIoyZB2J9LZkw9qb9sRRJAr2gpWGBD4CCmPZ8d17ZGDcIr8o+eI+bo5eKf+1j
|
||||||
|
6NhsjM7AmIccStNxZYWE4ZucvYYbPvT3ns/TNa7BH2DBqfGK84PawosGGBsIAAAA
|
||||||
|
LAUCaf8NSQIbDCIhBocrNKug/gxVj58EKFrbl9Ji+qLx9pAo1pg7nwBZgdPkAAAA
|
||||||
|
ADrcEIqnwTwJoiZAxzK+w7uQFHzsYMWIj8x+DKsn7D1silKINHDnFSrlSKRtbAW6
|
||||||
|
x9+HrN/nvR7bOnXZvZhz7lQ3Lp3YUdzEcqRMj8BWW8IXdm0C
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
||||||
Reference in New Issue
Block a user