mirror of
https://github.com/chatmail/core.git
synced 2026-07-23 03:13:06 +03:00
Compare commits
75 Commits
d2weber/qt
...
link2xt/ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31e83160f2 | ||
|
|
0e4574cd41 | ||
|
|
3a913dfb07 | ||
|
|
bdd9d96844 | ||
|
|
4e2238a38f | ||
|
|
8c53f86fbc | ||
|
|
ca1fe8e352 | ||
|
|
61898b4789 | ||
|
|
5c1e695237 | ||
|
|
1721c0316a | ||
|
|
398ddac0b9 | ||
|
|
35d7b4e21c | ||
|
|
03725c5f32 | ||
|
|
a0c96f4ce1 | ||
|
|
b432d08aa8 | ||
|
|
85db29bdfb | ||
|
|
23c216c84b | ||
|
|
d34dd40aa0 | ||
|
|
ebd1c20997 | ||
|
|
0c40b52ec8 | ||
|
|
aadd11a771 | ||
|
|
17b32a17f5 | ||
|
|
355ab8b3bc | ||
|
|
e2b67d20fb | ||
|
|
0560c13ae5 | ||
|
|
1a24bf612a | ||
|
|
492741ac60 | ||
|
|
d940f96a5c | ||
|
|
e5c920f1a1 | ||
|
|
5d0e5efc94 | ||
|
|
94fc4f7550 | ||
|
|
ce2d237ffd | ||
|
|
f2d11305bd | ||
|
|
5adf7454ea | ||
|
|
079fb352e3 | ||
|
|
823b0741df | ||
|
|
6322758482 | ||
|
|
90ac058043 | ||
|
|
212aaaad77 | ||
|
|
31b2d7961a | ||
|
|
2b6515e3b5 | ||
|
|
cac8c06c78 | ||
|
|
446cdabd21 | ||
|
|
d9ab7a7a43 | ||
|
|
e5d56a3b70 | ||
|
|
ee1095be9d | ||
|
|
520ede1fa5 | ||
|
|
89a1f55a87 | ||
|
|
90e6fa055f | ||
|
|
093ebe537f | ||
|
|
fa91b168a9 | ||
|
|
8c12804257 | ||
|
|
5beef1a2d0 | ||
|
|
cc08d5de81 | ||
|
|
ba1063ad0f | ||
|
|
30bdcd6809 | ||
|
|
5eb5d22d7b | ||
|
|
a22ba021e5 | ||
|
|
6e633eebe1 | ||
|
|
a14e617059 | ||
|
|
b4471f2434 | ||
|
|
abaa48d0a1 | ||
|
|
1b2ec2e88c | ||
|
|
f6e819d2a5 | ||
|
|
abb84efc37 | ||
|
|
09d5d0bddf | ||
|
|
b38277c294 | ||
|
|
975fd8aee3 | ||
|
|
5a54e18fee | ||
|
|
8b80ac146f | ||
|
|
d1f4e59d82 | ||
|
|
0a82d73eb5 | ||
|
|
c026910fa7 | ||
|
|
2aba6aa339 | ||
|
|
93a6f35ef7 |
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -20,7 +20,7 @@ permissions: {}
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_VERSION: 1.95.0
|
||||
RUST_VERSION: 1.97.1
|
||||
|
||||
# Minimum Supported Rust Version
|
||||
MSRV: 1.89.0
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
shell: bash
|
||||
if: matrix.rust == 'latest'
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
cache-bin: false
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154
|
||||
uses: taiki-e/install-action@50414676f9f5d50a65992c6dd2ed02641263226c
|
||||
with:
|
||||
tool: nextest
|
||||
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -226,7 +226,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -302,7 +302,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
29
.github/workflows/deltachat-rpc-server.yml
vendored
29
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
contents: write
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -397,7 +397,7 @@ jobs:
|
||||
# Needed to publish the binaries to the release.
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -513,15 +513,12 @@ jobs:
|
||||
deltachat-rpc-server/npm-package/*.tgz
|
||||
|
||||
# Configure Node.js for publishing.
|
||||
- uses: actions/setup-node@v6
|
||||
# Check <https://docs.npmjs.com/trusted-publishers> for the version requirements.
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed.
|
||||
# It is needed for <https://docs.npmjs.com/trusted-publishers>
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
package-manager-cache: false # never use caching in release builds
|
||||
|
||||
- name: Publish npm packets for prebuilds and `@deltachat/stdio-rpc-server`
|
||||
if: github.event_name == 'release'
|
||||
|
||||
2
.github/workflows/dev-version.yml
vendored
2
.github/workflows/dev-version.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Check that current version ends with -dev
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
18
.github/workflows/jsonrpc-client-npm-package.yml
vendored
18
.github/workflows/jsonrpc-client-npm-package.yml
vendored
@@ -17,23 +17,21 @@ jobs:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
# Configure Node.js for publishing.
|
||||
# Check <https://docs.npmjs.com/trusted-publishers> for the version requirements.
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed.
|
||||
# It is needed for <https://docs.npmjs.com/trusted-publishers>
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
package-manager-cache: false # never use caching in release builds
|
||||
|
||||
- name: Install dependencies without running scripts
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install --ignore-scripts
|
||||
|
||||
- name: Package
|
||||
@@ -43,5 +41,5 @@ jobs:
|
||||
npm pack .
|
||||
|
||||
- name: Publish
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm publish --provenance deltachat-jsonrpc-client-* --access public
|
||||
|
||||
16
.github/workflows/jsonrpc.yml
vendored
16
.github/workflows/jsonrpc.yml
vendored
@@ -16,30 +16,30 @@ jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v6
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 18.x
|
||||
node-version: 24
|
||||
- name: Add Rust cache
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
- name: Build TypeScript, run Rust tests, generate bindings
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run build
|
||||
- name: Run integration tests
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run test
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
||||
- name: Run linter
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run prettier:check
|
||||
|
||||
6
.github/workflows/nix.yml
vendored
6
.github/workflows/nix.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
name: check flake formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
#- deltachat-rpc-server-x86_64-android
|
||||
#- deltachat-rpc-server-x86-android
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
# because of <https://github.com/NixOS/nixpkgs/issues/413910>.
|
||||
# - deltachat-rpc-server-aarch64-darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
2
.github/workflows/repl.yml
vendored
2
.github/workflows/repl.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Build REPL example
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
26
.github/workflows/upload-docs.yml
vendored
26
.github/workflows/upload-docs.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
url: https://rs.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.RS_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc "${{ secrets.RS_DOCS_SSH_USER }}@rs.delta.chat:/var/www/html/rs.delta.chat/"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.RS_DOCS_SSH_USER }}@rs.delta.chat:"
|
||||
|
||||
build-python:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
url: https://py.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.PY_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.PY_DOCS_SSH_USER }}@py.delta.chat:/var/www/html/py.delta.chat"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.PY_DOCS_SSH_USER }}@py.delta.chat:"
|
||||
|
||||
build-c:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
url: https://c.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.C_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.C_DOCS_SSH_USER }}@c.delta.chat:/var/www/html/c.delta.chat"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.C_DOCS_SSH_USER }}@c.delta.chat:"
|
||||
|
||||
build-ts:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -81,18 +81,18 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./deltachat-jsonrpc-bindings/typescript
|
||||
working-directory: ./deltachat-jsonrpc/typescript
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: 24
|
||||
- name: npm install
|
||||
run: npm install
|
||||
- name: npm run build
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.JS_JSONRPC_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc-bindings/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:"
|
||||
|
||||
build-cffi:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
url: https://cffi.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -125,4 +125,4 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CFFI_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.CFFI_DOCS_SSH_USER }}@delta.chat:/var/www/html/cffi.delta.chat/"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.CFFI_DOCS_SSH_USER }}@delta.chat:"
|
||||
|
||||
4
.github/workflows/zizmor-scan.yml
vendored
4
.github/workflows/zizmor-scan.yml
vendored
@@ -18,9 +18,9 @@ jobs:
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||
|
||||
130
CHANGELOG.md
130
CHANGELOG.md
@@ -1,5 +1,132 @@
|
||||
# Changelog
|
||||
|
||||
## [2.56.0] - 2026-07-21
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] remove all oauth support and drop DC_LP_AUTH flags.
|
||||
- removed oauth2 module, dc_get_oauth2_url FFI function, DC_LP_AUTH flags and configured/serverflags, and the oauth2 parameter/field from SMTP/IMAP clients, JSON-RPC interfaces, and CLI tools.
|
||||
|
||||
also contains regenerated provider data after dropping oauth in the update script.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- do not set backup_time in exported databases.
|
||||
|
||||
### Fixes
|
||||
|
||||
- revert 207c2e6e4c1bec43204c3b8a46fcbbff67d54b3f because some users reported problems with it.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- bump version to 2.56.0-dev.
|
||||
|
||||
## [2.55.0] - 2026-07-20
|
||||
|
||||
Minor release to fix CI because releasing 2.54.0 failed.
|
||||
|
||||
### CI
|
||||
|
||||
- Update Node version to 24.
|
||||
|
||||
## [2.54.0] - 2026-07-20
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] Deprecate `is_chatmail`.
|
||||
- UIs should not behave differently for chatmail relays than for classical email servers; most usages of `is_chatmail` can be replaced by `force_encryption`.
|
||||
- [**breaking**] `delete_transport()` must not be used by UIs anymore. Instead, `set_transport_unpublished()` must be called when a user clicks on "Remove".
|
||||
- [**breaking**] `list_transports()` doesn't return unpublished relays anymore.
|
||||
- UIs should use `list_transports()` rather than `list_transports_ex()`, because unpublished transports count as removed from the user point of view, and should not be shown in the relay list anymore.
|
||||
- deltachat-rpc-client: add `Account.set_transport_unpublished()`.
|
||||
- Add `MsgReadCountChanged` event.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Implement support for populating and maintaining a list of default relays ([#8341](https://github.com/chatmail/core/pull/8341)).
|
||||
- Remove hidden relays automatically ([#8402](https://github.com/chatmail/core/pull/8402)).
|
||||
- Automatically remove oldest unpublished relay in order to make space when the user wants to add more; don't allow more than 5 relays overall ([#8428](https://github.com/chatmail/core/pull/8428)).
|
||||
- Add silent group changes messages as InNoticed, not InSeen.
|
||||
- Remove `?emailaddress` argument from autoconfig URL that is not using a dedicated domain.
|
||||
- Remove `imap::Session::sync_seen_flags()` ([#7742](https://github.com/chatmail/core/pull/7742)).
|
||||
- Use CAPABILITY response code if IMAP LOGIN command returns it.
|
||||
- Increase max idle timeout for iroh backup receiver to 60 seconds.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Request MDNs for resent channel messages.
|
||||
- Make pre-messages w/o text want MDNs ([#8004](https://github.com/chatmail/core/pull/8004)).
|
||||
- Make truncated edited messages have HTML for receivers ([#8249](https://github.com/chatmail/core/pull/8249)).
|
||||
- Un-escape message footer marks in full messages (`get_html`) ([#8427](https://github.com/chatmail/core/pull/8427)).
|
||||
- Hide synced chat if we only know its visibility ([#8343](https://github.com/chatmail/core/pull/8343)).
|
||||
- Tombstone MDN before sending it ([#8252](https://github.com/chatmail/core/pull/8252)).
|
||||
- Recreate `imap_markseen` with `PRIMARY KEY` constraint.
|
||||
- Rerun the full securejoin protocol if the address was outdated ([#8358](https://github.com/chatmail/core/pull/8358)).
|
||||
- Return early from `receive_imf` to not tombstone Iroh-Node-Addr message if webxdc instance isn't found ([#8372](https://github.com/chatmail/core/pull/8372)).
|
||||
- Replace `last_added_location_id` with `last_added_location_timestamp`.
|
||||
- Do not put locations into pre-messages.
|
||||
- RUSTSEC-2026-0204 ([#8403](https://github.com/chatmail/core/pull/8403)).
|
||||
- Ensure public key signatures are not in the past compared to the public key.
|
||||
- Do not bubble up errors in IMAP candidate loop.
|
||||
- Do not log errors if full message is not available on any transport.
|
||||
- Apply reactions that arrived before the message at later time ([#8415](https://github.com/chatmail/core/pull/8415)).
|
||||
|
||||
### Performance
|
||||
|
||||
- Add timestamp to `msgs_index7` and speed up `Chatlist::try_load()` ([#7848](https://github.com/chatmail/core/pull/7848)).
|
||||
|
||||
### CI
|
||||
|
||||
- Update Rust to 1.97.1.
|
||||
- rrsync prepends the restricted upload path, we need to leave it out ([#8405](https://github.com/chatmail/core/pull/8405)).
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update STYLE.md: macros should be used only when necessary ([#8410](https://github.com/chatmail/core/pull/8410)).
|
||||
- `create_group_chat_unencrypted()` may lead to chat split on the first device.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Deprecate unused `SkipAutocrypt` param.
|
||||
- Remove commented out `RenderedEmail.envelope`.
|
||||
- Remove the ability to send messages with non-standard header protection.
|
||||
- Make `crate::pgp::symm_encrypt_message` non-async.
|
||||
- Move `ensure_secret_key_exists` into key.rs.
|
||||
- Improve comment ([#8366](https://github.com/chatmail/core/pull/8366)).
|
||||
- Remove `set_modseq()` function.
|
||||
- Remove unnecessary reference in format string.
|
||||
- Label the loop iterating over the candidates.
|
||||
- Remove `GROUP BY c.id` from chatlist queries.
|
||||
|
||||
### Tests
|
||||
|
||||
- securejoin: Check that "vc-{,request-}pubkey" messages don't contain displayname.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- bump version to 2.54.0-dev.
|
||||
- deps: bump taiki-e/install-action from 2.81.1 to 2.81.8.
|
||||
- deps: bump taiki-e/install-action from 2.81.8 to 2.81.11.
|
||||
- update rPGP from 0.19.0 to 0.20.0.
|
||||
- update astral-tokio-tar from 0.6.2 to 0.6.3.
|
||||
- deps: bump anyhow to 1.0.103.
|
||||
- deps: bump actions/checkout from 6 to 7.
|
||||
- cargo: bump syn from 2.0.117 to 2.0.118.
|
||||
- cargo: bump quote from 1.0.45 to 1.0.46.
|
||||
- cargo: bump bytes from 1.11.1 to 1.12.0.
|
||||
- cargo: bump regex from 1.12.3 to 1.12.4.
|
||||
- cargo: bump log from 0.4.31 to 0.4.33.
|
||||
- cargo: bump hyper from 1.9.0 to 1.10.1.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.6 to 0.5.7.
|
||||
- cargo: bump chrono from 0.4.44 to 0.4.45.
|
||||
- update quick-xml to 0.41.0.
|
||||
- cargo: bump brotli from 8.0.2 to 8.0.4.
|
||||
- cargo: bump smallvec from 1.15.1 to 1.15.2.
|
||||
- deps: bump taiki-e/install-action from 2.81.11 to 2.82.6.
|
||||
- update yanked spin@0.9.8 and spin@0.10.0.
|
||||
- deps: bump taiki-e/install-action from 2.82.6 to 2.82.10.
|
||||
- update async-imap to 0.11.3.
|
||||
|
||||
## [2.53.0] - 2026-06-15
|
||||
|
||||
### Features / Changes
|
||||
@@ -8374,3 +8501,6 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[2.51.0]: https://github.com/chatmail/core/compare/v2.50.0..v2.51.0
|
||||
[2.52.0]: https://github.com/chatmail/core/compare/v2.51.0..v2.52.0
|
||||
[2.53.0]: https://github.com/chatmail/core/compare/v2.52.0..v2.53.0
|
||||
[2.54.0]: https://github.com/chatmail/core/compare/v2.53.0..v2.54.0
|
||||
[2.55.0]: https://github.com/chatmail/core/compare/v2.54.0..v2.55.0
|
||||
[2.56.0]: https://github.com/chatmail/core/compare/v2.55.0..v2.56.0
|
||||
|
||||
@@ -2,51 +2,45 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(deltachat LANGUAGES C)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
option(WITH_JSONRPC_BINDINGS "Generate jsonrpc bindings" OFF)
|
||||
|
||||
find_program(CARGO cargo)
|
||||
|
||||
if(APPLE)
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
elseif(UNIX)
|
||||
set(DYNAMIC_EXT "so")
|
||||
set(DYNAMIC_EXT "so")
|
||||
else()
|
||||
set(DYNAMIC_EXT "dll")
|
||||
set(DYNAMIC_EXT "dll")
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{CARGO_BUILD_TARGET})
|
||||
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/$ENV{CARGO_BUILD_TARGET}/release")
|
||||
set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}")
|
||||
else()
|
||||
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/release")
|
||||
set(ARCH_DIR "./")
|
||||
endif()
|
||||
|
||||
if(WITH_JSONRPC_BINDINGS)
|
||||
set(JSONRPC_ARGS --package deltachat-jsonrpc-bindings)
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
lib_deltachat
|
||||
ALL
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env
|
||||
CARGO_TARGET_DIR="${CMAKE_BINARY_DIR}/target"
|
||||
PREFIX="${CMAKE_INSTALL_PREFIX}"
|
||||
LIBDIR="${CMAKE_INSTALL_FULL_LIBDIR}"
|
||||
INCLUDEDIR="${CMAKE_INSTALL_FULL_INCLUDEDIR}"
|
||||
${CARGO} build --release --package deltachat_ffi ${JSONRPC_ARGS}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
COMMAND
|
||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
||||
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
|
||||
)
|
||||
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
install(FILES "${CARGO_OUT_DIR}/libdeltachat.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
install(FILES "${CARGO_OUT_DIR}/libdeltachat.${DYNAMIC_EXT}" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
install(FILES "${CARGO_OUT_DIR}/pkgconfig/deltachat.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
add_custom_target(
|
||||
lib_deltachat
|
||||
ALL
|
||||
DEPENDS
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
)
|
||||
|
||||
if(WITH_JSONRPC_BINDINGS)
|
||||
set(JSONRPC_HPP "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc")
|
||||
install(FILES "${JSONRPC_HPP}/generated/types.hpp" "${JSONRPC_HPP}/generated/client.hpp"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc/generated")
|
||||
install(FILES "${JSONRPC_HPP}/cffi_client.hpp"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc")
|
||||
endif()
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
336
Cargo.lock
generated
336
Cargo.lock
generated
@@ -124,9 +124,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
@@ -177,7 +177,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -189,20 +189,20 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "astral-tokio-tar"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb50a7aae84a03bf55b067832bc376f4961b790c97e64d3eacee97d389b90277"
|
||||
checksum = "08648fef353ab39a9d26f909ad53fc4f071be4c91853b78523f5cc3d9e5ebffd"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"portable-atomic",
|
||||
"rustc-hash",
|
||||
"rustix 0.38.44",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
@@ -271,9 +271,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a78dceaba06f029d8f4d7df20addd4b7370a30206e3926267ecda2915b0f3f66"
|
||||
checksum = "9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570"
|
||||
dependencies = [
|
||||
"async-channel 2.5.0",
|
||||
"async-compression",
|
||||
@@ -339,7 +339,7 @@ checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -391,28 +391,6 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backon"
|
||||
version = "1.5.0"
|
||||
@@ -480,7 +458,7 @@ checksum = "57413e4b276d883b77fb368b7b33ae6a5eb97692852d49a5394d4f72ba961827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
@@ -621,7 +599,7 @@ dependencies = [
|
||||
"proc-macro-crate 2.0.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -660,9 +638,9 @@ checksum = "102dbef1187b1893e6dfe05a774e79fd52265f49f214f6879c8ff49f52c8188b"
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.2"
|
||||
version = "8.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
|
||||
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -720,9 +698,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -790,8 +768,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -852,9 +828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
@@ -945,15 +921,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
version = "0.2.3"
|
||||
@@ -1147,9 +1114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
@@ -1278,7 +1245,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1319,7 +1286,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1330,7 +1297,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1350,7 +1317,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -1412,7 +1379,6 @@ dependencies = [
|
||||
"sdp",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
"shadowsocks",
|
||||
@@ -1459,7 +1425,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.5.0",
|
||||
@@ -1478,16 +1444,9 @@ dependencies = [
|
||||
"yerpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc-bindings"
|
||||
version = "2.54.0-dev"
|
||||
dependencies = [
|
||||
"deltachat-jsonrpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1503,7 +1462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1527,12 +1486,12 @@ name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1581,7 +1540,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1611,7 +1570,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1621,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1650,7 +1609,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
@@ -1662,7 +1621,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
@@ -1717,7 +1676,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1728,7 +1687,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1767,12 +1726,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.18"
|
||||
@@ -1797,7 +1750,7 @@ checksum = "7a4102713839a8c01c77c165bc38ef2e83948f6397fa1e1dcfacec0f07b149d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1926,7 +1879,7 @@ dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1946,7 +1899,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2086,18 +2039,6 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
@@ -2161,12 +2102,6 @@ dependencies = [
|
||||
name = "format-flowed"
|
||||
version = "1.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -2198,7 +2133,7 @@ dependencies = [
|
||||
"diatomic-waker",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"spin 0.9.8",
|
||||
"spin 0.9.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2270,7 +2205,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2410,9 +2345,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.7"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
|
||||
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -2688,9 +2623,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
|
||||
checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -2740,7 +2675,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.9",
|
||||
"socket2 0.6.3",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2884,7 +2819,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3161,7 +3096,7 @@ dependencies = [
|
||||
"iroh-metrics-derive",
|
||||
"itoa",
|
||||
"serde",
|
||||
"snafu",
|
||||
"snafu 0.8.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -3174,7 +3109,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3293,16 +3228,6 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@@ -3361,7 +3286,7 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin 0.9.8",
|
||||
"spin 0.9.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3384,7 +3309,6 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"redox_syscall 0.5.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3443,9 +3367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.31"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
@@ -3531,9 +3455,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
@@ -3799,7 +3723,7 @@ dependencies = [
|
||||
"netlink-proto",
|
||||
"netlink-sys",
|
||||
"serde",
|
||||
"snafu",
|
||||
"snafu 0.8.5",
|
||||
"socket2 0.5.9",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -3887,7 +3811,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3931,7 +3855,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3992,7 +3916,7 @@ dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4088,7 +4012,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4275,7 +4199,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4291,9 +4215,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pgp"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaffe1ec22db286599c30ae6be75b37493b558735d86c8e59ec5c38794415fe4"
|
||||
checksum = "1cfa4743b28656065ff4c0ba09e46b357a65e8c00fc2341e89084b82f87cbdf1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
@@ -4332,6 +4256,7 @@ dependencies = [
|
||||
"k256",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"ml-dsa",
|
||||
"ml-kem",
|
||||
"nom 8.0.0",
|
||||
@@ -4343,7 +4268,6 @@ dependencies = [
|
||||
"p384",
|
||||
"p521",
|
||||
"rand 0.8.6",
|
||||
"regex",
|
||||
"replace_with",
|
||||
"ripemd",
|
||||
"rsa",
|
||||
@@ -4354,7 +4278,8 @@ dependencies = [
|
||||
"signature",
|
||||
"slh-dsa",
|
||||
"smallvec",
|
||||
"snafu",
|
||||
"snafu 0.9.1",
|
||||
"subtle",
|
||||
"twofish",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
@@ -4387,7 +4312,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4506,7 +4431,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4593,7 +4518,7 @@ dependencies = [
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"snafu",
|
||||
"snafu 0.8.5",
|
||||
"socket2 0.5.9",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -4738,7 +4663,7 @@ dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4804,9 +4729,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.2"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -4866,9 +4791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -5056,9 +4981,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
version = "1.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -5085,9 +5010,9 @@ checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "replace_with"
|
||||
@@ -5288,7 +5213,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5297,7 +5222,8 @@ version = "0.23.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"brotli",
|
||||
"brotli-decompressor",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
@@ -5343,7 +5269,6 @@ version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
@@ -5440,7 +5365,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5572,7 +5497,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5583,7 +5508,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5727,7 +5652,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"shadowsocks-crypto",
|
||||
"socket2 0.5.9",
|
||||
"spin 0.10.0",
|
||||
"spin 0.10.1",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-tfo",
|
||||
@@ -5840,9 +5765,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
@@ -5856,7 +5781,16 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019"
|
||||
dependencies = [
|
||||
"snafu-derive",
|
||||
"snafu-derive 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522"
|
||||
dependencies = [
|
||||
"snafu-derive 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5868,7 +5802,19 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5893,15 +5839,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
@@ -5965,7 +5911,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5977,7 +5923,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6048,9 +5994,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
version = "2.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6074,7 +6020,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6154,7 +6100,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6209,7 +6155,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6220,7 +6166,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6335,7 +6281,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6535,7 +6481,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6585,7 +6531,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6630,7 +6576,7 @@ dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6831,7 +6777,7 @@ dependencies = [
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -6866,7 +6812,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -7049,7 +6995,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7060,7 +7006,7 @@ checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7071,7 +7017,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7082,7 +7028,7 @@ checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7514,7 +7460,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "yerpc"
|
||||
version = "0.6.4"
|
||||
source = "git+https://github.com/d2weber/yerpc.git?branch=qt_bindings#ba7aead1ffa49dc89bb85322aba38a65a0694e7b"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dc24983fbe850227bfc1de89bf8cbfb3e2463afc322e0de2f155c4c23d06445"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 1.9.0",
|
||||
@@ -7532,14 +7479,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yerpc_derive"
|
||||
version = "0.6.4"
|
||||
source = "git+https://github.com/d2weber/yerpc.git?branch=qt_bindings#ba7aead1ffa49dc89bb85322aba38a65a0694e7b"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d8560d021437420316370db865e44c000bf86380b47cf05e49be9d652042bf5"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7562,7 +7510,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -7590,7 +7538,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7610,7 +7558,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -7631,7 +7579,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7653,7 +7601,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.89"
|
||||
@@ -44,7 +44,7 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
||||
anyhow = { workspace = true }
|
||||
async-broadcast = "0.7.2"
|
||||
async-channel = { workspace = true }
|
||||
async-imap = { version = "0.11.1", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-imap = { version = "0.11.3", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-native-tls = { version = "0.6", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.18", default-features = false, features = ["deflate", "tokio-fs"] }
|
||||
@@ -78,10 +78,10 @@ num-derive = "0.4"
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = "0.12.4"
|
||||
percent-encoding = "2.3"
|
||||
pgp = { version = "0.19.0", features = ["draft-pqc"], default-features = false }
|
||||
pgp = { version = "0.20.0", features = ["draft-pqc"], default-features = false }
|
||||
pin-project = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = { version = "0.39", features = ["escape-html"] }
|
||||
quick-xml = { version = "0.41", features = ["escape-html"] }
|
||||
rand-old = { package = "rand", version = "0.8" }
|
||||
rand = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -89,7 +89,6 @@ rusqlite = { workspace = true, features = ["sqlcipher"] }
|
||||
sanitize-filename = { workspace = true }
|
||||
sdp = "0.17.1"
|
||||
serde_json = { workspace = true }
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
@@ -101,9 +100,9 @@ tagger = "4.3.4"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = { workspace = true }
|
||||
tokio-io-timeout = "1.2.1"
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["aws-lc-rs", "tls12"] }
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["tls12", "brotli"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
astral-tokio-tar = { version = "0.6.2", default-features = false }
|
||||
astral-tokio-tar = { version = "0.6.3", default-features = false }
|
||||
tokio-util = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.9"
|
||||
@@ -130,7 +129,6 @@ members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc",
|
||||
"deltachat-jsonrpc-bindings",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-ratelimit",
|
||||
"deltachat-repl",
|
||||
@@ -185,7 +183,6 @@ base64 = "0.22"
|
||||
chrono = { version = "0.4.44", default-features = false }
|
||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
||||
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
|
||||
deltachat-jsonrpc-bindings = { path = "deltachat-jsonrpc-bindings", default-features = false }
|
||||
deltachat = { path = ".", default-features = false }
|
||||
futures = "0.3.32"
|
||||
futures-lite = "2.6.1"
|
||||
@@ -205,7 +202,7 @@ thiserror = "2"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7.18"
|
||||
tracing-subscriber = "0.3"
|
||||
yerpc = { git="https://github.com/d2weber/yerpc.git", branch="qt_bindings" }
|
||||
yerpc = "0.6.4"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
|
||||
7
STYLE.md
7
STYLE.md
@@ -181,3 +181,10 @@ Calling `into()`, `try_into()` or `parse()`
|
||||
creates an indirection,
|
||||
which is hard to follow for people who are not familiar with Rust,
|
||||
or who are not using rust-analyzer.
|
||||
|
||||
## Use macros only when really needed
|
||||
|
||||
Macros can be hard to read for people unfamiliar with Rust,
|
||||
and can have surprising effects like evaluating arguments multiple times.
|
||||
Therefore, macros should only be used when really needed;
|
||||
using functions is usually better.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat-contact-tools"
|
||||
version = "0.0.0" # No semver-stable versioning
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
description = "Contact-related tools, like parsing vcards and sanitizing name and address. Meant for internal use in the deltachat crate."
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -31,15 +31,15 @@ use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
use regex::Regex;
|
||||
|
||||
mod vcard;
|
||||
pub use vcard::{make_vcard, parse_vcard, VcardContact};
|
||||
pub use vcard::{VcardContact, make_vcard, parse_vcard};
|
||||
|
||||
/// Valid contact address.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ContactAddress(String);
|
||||
|
||||
impl Deref for ContactAddress {
|
||||
|
||||
@@ -220,7 +220,10 @@ END:VCARD
|
||||
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
||||
assert_eq!(contacts[0].authname, "Bob".to_string());
|
||||
assert_eq!(contacts[0].key, None);
|
||||
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
|
||||
assert_eq!(
|
||||
contacts[0].profile_image.as_deref().unwrap(),
|
||||
"/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q=="
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +247,10 @@ END:VCARD",
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
||||
assert_eq!(&contacts[0].authname, "Alice Wonderland");
|
||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
assert_eq!(
|
||||
contacts[0].key.as_ref().unwrap(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
);
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
}
|
||||
@@ -272,9 +278,15 @@ END:VCARD",
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
||||
assert_eq!(&contacts[0].authname, "Alice");
|
||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==");
|
||||
assert_eq!(
|
||||
contacts[0].key.as_ref().unwrap(),
|
||||
"xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=="
|
||||
);
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
assert_eq!(contacts[0].profile_image.as_ref().unwrap(), "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z");
|
||||
assert_eq!(
|
||||
contacts[0].profile_image.as_ref().unwrap(),
|
||||
"/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -462,7 +462,10 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
|
||||
* seconds. 2 days by default.
|
||||
* This is not supposed to be changed by UIs and only used for testing.
|
||||
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
|
||||
* - `is_chatmail` = (deprecated) 1 if the the server is a chatmail server, 0 otherwise.
|
||||
* This is deprecated, UIs should not behave differently
|
||||
* for chatmail relays and classical email servers.
|
||||
* Most usages in UIs can be replaced by `force_encryption`.
|
||||
* - `is_muted` = Whether a context is muted by the user.
|
||||
* Muted contexts should not sound, vibrate or show notifications.
|
||||
* In contrast to `dc_set_chat_mute_duration()`,
|
||||
@@ -500,7 +503,6 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* - `send_pw` = SMTP-password, guessed if left out
|
||||
* - `send_port` = SMTP-port, guessed if left out
|
||||
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
|
||||
* - `proxy_enabled` = Proxy enabled. Disabled by default.
|
||||
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
|
||||
* - `imap_certificate_checks` = how to check IMAP and SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||
@@ -587,36 +589,7 @@ int dc_set_config_from_qr (dc_context_t* context, const char* qr);
|
||||
char* dc_get_info (const dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Get URL that can be used to initiate an OAuth2 authorization.
|
||||
*
|
||||
* If an OAuth2 authorization is possible for a given e-mail address,
|
||||
* this function returns the URL that should be opened in a browser.
|
||||
*
|
||||
* If the user authorizes access,
|
||||
* the given redirect_uri is called by the provider.
|
||||
* It's up to the UI to handle this call.
|
||||
*
|
||||
* The provider will attach some parameters to the URL,
|
||||
* most important the parameter `code` that should be set as the `mail_pw`.
|
||||
* With `server_flags` set to #DC_LP_AUTH_OAUTH2,
|
||||
* dc_configure() can be called as usual afterwards.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param addr E-mail address the user has entered.
|
||||
* In case the user selects a different e-mail address during
|
||||
* authorization, this is corrected in dc_configure()
|
||||
* @param redirect_uri URL that will get `code` that is used as `mail_pw` then.
|
||||
* Not all URLs are allowed here, however, the following should work:
|
||||
* `chat.delta:/PATH`, `http://localhost:PORT/PATH`,
|
||||
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
|
||||
* (the latter just displays the code the user can copy+paste then)
|
||||
* @return URL that can be opened in the browser to start OAuth2.
|
||||
* Returned strings must be released using dc_str_unref().
|
||||
* If OAuth2 is not possible for the given e-mail address, NULL is returned.
|
||||
*/
|
||||
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
|
||||
|
||||
|
||||
|
||||
#define DC_CONNECTIVITY_NOT_CONNECTED 1000
|
||||
@@ -710,7 +683,7 @@ int dc_get_push_state (dc_context_t* context);
|
||||
* to get the full configuration from well-known URLs.
|
||||
*
|
||||
* - If _more_ options as `mail_server`, `mail_port`, `send_server`,
|
||||
* `send_port`, `send_user` or `server_flags` are specified,
|
||||
* `send_port` or `send_user` are specified,
|
||||
* **autoconfigure/autodiscover is skipped**.
|
||||
*
|
||||
* While dc_configure() returns immediately,
|
||||
@@ -5733,41 +5706,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_LP DC_LP
|
||||
*
|
||||
* Flags for configuring IMAP and SMTP servers.
|
||||
* These flags are optional
|
||||
* and may be set together with the username, password etc.
|
||||
* via dc_set_config() using the key "server_flags".
|
||||
*
|
||||
* @addtogroup DC_LP
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||
* Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
|
||||
* the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||
*/
|
||||
#define DC_LP_AUTH_OAUTH2 0x2
|
||||
|
||||
|
||||
/**
|
||||
* Force NORMAL authorization, this is the default.
|
||||
* If this flag is set, automatic configuration is skipped.
|
||||
*/
|
||||
#define DC_LP_AUTH_NORMAL 0x4
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is chosen
|
||||
|
||||
/**
|
||||
* @defgroup DC_CERTCK DC_CERTCK
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,13 +17,15 @@ use std::ptr;
|
||||
/// }
|
||||
/// ```
|
||||
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
} else {
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret
|
||||
unsafe {
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
} else {
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for the [OsStrExt] trait
|
||||
@@ -164,34 +166,40 @@ pub(crate) trait Strdup {
|
||||
/// This function will panic when the original string contains an
|
||||
/// interior null byte as this can not be represented in raw C
|
||||
/// strings.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl Strdup for str {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
unsafe {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for String {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
let s: &str = self;
|
||||
s.strdup()
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for std::path::Path {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
unsafe {
|
||||
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for [u8] {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
unsafe {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,15 +217,15 @@ pub(crate) trait OptStrdup {
|
||||
/// Allocate a new raw C `*char` version of this string, or NULL.
|
||||
///
|
||||
/// See [Strdup::strdup] for details.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> OptStrdup for Option<T> {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
match self {
|
||||
Some(s) => {
|
||||
let tmp = CString::new_lossy(s.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
unsafe { dc_strdup(tmp.as_ptr()) }
|
||||
}
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
@@ -258,11 +266,9 @@ pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
unsafe {
|
||||
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
|
||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||
std::path::Path::new(os_str)
|
||||
}
|
||||
let c_str = unsafe { std::ffi::CStr::from_ptr(s) }.to_bytes();
|
||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||
std::path::Path::new(os_str)
|
||||
}
|
||||
|
||||
// as_path() implementation for windows, documented above.
|
||||
|
||||
1
deltachat-jsonrpc-bindings/.gitignore
vendored
1
deltachat-jsonrpc-bindings/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
generated
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc-bindings"
|
||||
version = "2.54.0-dev"
|
||||
description = "Autogenerate DeltaChat JSON-RPC API bindings at build time"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[build-dependencies]
|
||||
deltachat-jsonrpc = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat-jsonrpc/vendored"]
|
||||
@@ -1,7 +0,0 @@
|
||||
use deltachat_jsonrpc::api::{write_qt_bindings, write_ts_bindings};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
write_ts_bindings(Path::new("typescript/generated"));
|
||||
write_qt_bindings(Path::new("qt/generated"), "deltachat");
|
||||
}
|
||||
1
deltachat-jsonrpc-bindings/qt/.gitignore
vendored
1
deltachat-jsonrpc-bindings/qt/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
generated
|
||||
@@ -1,104 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "deltachat-jsonrpc/generated/types.hpp"
|
||||
#include "deltachat-jsonrpc/generated/client.hpp"
|
||||
#include "deltachat.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace deltachat {
|
||||
|
||||
class CffiTransport : public Transport {
|
||||
public:
|
||||
explicit CffiTransport(dc_accounts_t* accounts)
|
||||
: jsonrpc_(dc_jsonrpc_init(accounts))
|
||||
{
|
||||
if (!jsonrpc_) std::abort();
|
||||
thread_ = std::thread([this] { run(); });
|
||||
}
|
||||
|
||||
virtual ~CffiTransport() {
|
||||
done_ = true;
|
||||
// Unblock dc_jsonrpc_next_response by sending a dummy request
|
||||
if (jsonrpc_) dc_jsonrpc_request(jsonrpc_, "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"get_system_info\"}");
|
||||
if (thread_.joinable()) thread_.join();
|
||||
std::lock_guard lk(mu_);
|
||||
for (auto& [id, prom] : pending_) {
|
||||
prom.set_value({{}, "Transport destructed", -32060});
|
||||
}
|
||||
pending_.clear();
|
||||
if (jsonrpc_) dc_jsonrpc_unref(jsonrpc_);
|
||||
}
|
||||
|
||||
virtual std::future<Result<QJsonValue>> send(const QString method, const QJsonValue params) override {
|
||||
uint32_t id = next_id_++;
|
||||
QJsonObject envelope{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", static_cast<qint64>(id)},
|
||||
{"method", method},
|
||||
{"params", params},
|
||||
};
|
||||
|
||||
std::promise<Result<QJsonValue>> prom;
|
||||
std::future<Result<QJsonValue>> fut = prom.get_future();
|
||||
|
||||
{
|
||||
std::lock_guard lk(mu_);
|
||||
pending_[id] = std::move(prom);
|
||||
}
|
||||
|
||||
QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact);
|
||||
dc_jsonrpc_request(jsonrpc_, json.constData());
|
||||
return fut;
|
||||
}
|
||||
private:
|
||||
void run() {
|
||||
while (!done_) {
|
||||
char* raw_json = dc_jsonrpc_next_response(jsonrpc_);
|
||||
if (!raw_json) {
|
||||
break;
|
||||
}
|
||||
QByteArray json{raw_json};
|
||||
dc_str_unref(raw_json);
|
||||
if (done_) break;
|
||||
|
||||
QJsonObject obj = QJsonDocument::fromJson(json).object();
|
||||
|
||||
if (!obj["id"].isDouble()) {
|
||||
qCritical() << "No valid rpc id in" << QString{json};
|
||||
continue;
|
||||
}
|
||||
uint32_t id = static_cast<uint32_t>(obj["id"].toInt());
|
||||
|
||||
std::promise<Result<QJsonValue>> prom;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mu_);
|
||||
if (auto nh = pending_.extract(id)) {
|
||||
prom = std::move(nh.mapped());
|
||||
} else {
|
||||
qCritical() << "Could not map response" << QString{json};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prom.set_value(parseResult(obj));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
dc_jsonrpc_instance_t* jsonrpc_;
|
||||
std::thread thread_;
|
||||
std::mutex mu_;
|
||||
std::atomic<uint32_t> next_id_{1};
|
||||
std::atomic<bool> done_{false};
|
||||
std::unordered_map<uint32_t, std::promise<Result<QJsonValue>>> pending_;
|
||||
};
|
||||
|
||||
class CffiDeltaChat : public RawClient {
|
||||
public:
|
||||
explicit CffiDeltaChat(dc_accounts_t* accounts)
|
||||
: RawClient(std::make_unique<CffiTransport>(accounts)) {}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
|
||||
@@ -5,26 +5,27 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use anyhow::{Context, Result, anyhow, bail, ensure};
|
||||
use deltachat::EventEmitter;
|
||||
pub use deltachat::accounts::Accounts;
|
||||
use deltachat::blob::BlobObject;
|
||||
use deltachat::calls::ice_servers;
|
||||
use deltachat::chat::{
|
||||
self, add_contact_to_chat, forward_msgs, forward_msgs_2ctx, get_chat_media, get_chat_msgs,
|
||||
get_chat_msgs_ex, markfresh_chat, marknoticed_all_chats, marknoticed_chat,
|
||||
remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
|
||||
self, Chat, ChatId, ChatItem, MessageListOptions, add_contact_to_chat, forward_msgs,
|
||||
forward_msgs_2ctx, get_chat_media, get_chat_msgs, get_chat_msgs_ex, markfresh_chat,
|
||||
marknoticed_all_chats, marknoticed_chat, remove_contact_from_chat,
|
||||
};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::config::{get_all_ui_config_keys, Config};
|
||||
use deltachat::config::{Config, get_all_ui_config_keys};
|
||||
use deltachat::constants::DC_MSG_ID_DAYMARKER;
|
||||
use deltachat::contact::{may_be_valid_addr, Contact, ContactId, Origin};
|
||||
use deltachat::contact::{Contact, ContactId, Origin, may_be_valid_addr};
|
||||
use deltachat::context::get_info;
|
||||
use deltachat::ephemeral::Timer;
|
||||
use deltachat::imex;
|
||||
use deltachat::location;
|
||||
use deltachat::message::{
|
||||
self, delete_msgs_ex, get_existing_msg_ids, get_msg_read_receipt_count, get_msg_read_receipts,
|
||||
markseen_msgs, Message, MessageState, MsgId, Viewtype,
|
||||
self, Message, MessageState, MsgId, Viewtype, delete_msgs_ex, get_existing_msg_ids,
|
||||
get_msg_read_receipt_count, get_msg_read_receipts, markseen_msgs,
|
||||
};
|
||||
use deltachat::peer_channels::{
|
||||
leave_webxdc_realtime, send_webxdc_realtime_advertisement, send_webxdc_realtime_data,
|
||||
@@ -37,10 +38,9 @@ use deltachat::securejoin;
|
||||
use deltachat::stock_str::StockMessage;
|
||||
use deltachat::storage_usage::{get_blobdir_storage_usage, get_storage_usage};
|
||||
use deltachat::webxdc::StatusUpdateSerial;
|
||||
use deltachat::EventEmitter;
|
||||
use sanitize_filename::is_sanitized;
|
||||
use tokio::fs;
|
||||
use tokio::sync::{watch, Mutex, RwLock};
|
||||
use tokio::sync::{Mutex, RwLock, watch};
|
||||
use types::login_param::EnteredLoginParam;
|
||||
use yerpc::rpc;
|
||||
|
||||
@@ -67,7 +67,7 @@ use self::types::{
|
||||
JsonrpcMessageListItem, MessageNotificationInfo, MessageSearchResult, MessageViewtype,
|
||||
},
|
||||
};
|
||||
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
|
||||
use crate::api::types::chat_list::{ChatListItemFetchResult, get_chat_list_item_by_id};
|
||||
use crate::api::types::login_param::TransportListEntry;
|
||||
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
|
||||
|
||||
@@ -157,7 +157,7 @@ impl CommandApi {
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc(all_positional)]
|
||||
#[rpc(all_positional, ts_outdir = "typescript/generated")]
|
||||
impl CommandApi {
|
||||
/// Test function.
|
||||
async fn sleep(&self, delay: f64) {
|
||||
@@ -526,7 +526,7 @@ impl CommandApi {
|
||||
/// - [Self::add_transport_from_qr()] to add a transport
|
||||
/// from a server encoded in a QR code.
|
||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
||||
/// - [Self::delete_transport()] to remove a transport.
|
||||
/// - [Self::set_transport_unpublished()] to remove a transport.
|
||||
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
|
||||
async fn add_or_update_transport(
|
||||
&self,
|
||||
@@ -552,23 +552,31 @@ impl CommandApi {
|
||||
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
/// Use [Self::list_transports_ex()] to additionally query
|
||||
/// whether the transports are marked as 'unpublished'.
|
||||
/// and [Self::set_transport_unpublished()] to remove a transport.
|
||||
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let res = ctx
|
||||
.list_transports()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|t| !t.is_unpublished)
|
||||
.map(|t| t.param.into())
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Deprecated 2026-06: This is not needed by UI implementations anymore,
|
||||
/// because unpublished relays now count as removed from the user point of view,
|
||||
/// and must not be shown in the list of relays.
|
||||
/// This means that UIs should use `list_transports()` instead of this function.
|
||||
///
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
///
|
||||
/// As opposed to `list_transports()`, this function also returns unpublished transports,
|
||||
/// and for each returned transport it returns the information whether or not is `unpublished`.
|
||||
///
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
/// and [Self::set_transport_unpublished()] to change whether a transport is 'published'.
|
||||
async fn list_transports_ex(&self, account_id: u32) -> Result<Vec<TransportListEntry>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let res = ctx
|
||||
@@ -580,23 +588,31 @@ impl CommandApi {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Removes the transport with the specified email address
|
||||
/// (i.e. [EnteredLoginParam::addr]).
|
||||
/// Immediately deletes a transport, potentially causing messages not to arrive.
|
||||
/// This must ONLY be used by the automated tests.
|
||||
/// UI implementations must use [`Self::set_transport_unpublished`] instead.
|
||||
async fn delete_transport(&self, account_id: u32, addr: String) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.delete_transport(&addr).await
|
||||
}
|
||||
|
||||
/// Change whether the transport is unpublished.
|
||||
/// UIs should call this function when the user clicks on "Remove".
|
||||
/// Core will keep listening on this transport for some time,
|
||||
/// and automatically remove it once it is no longer needed.
|
||||
///
|
||||
/// Unpublished transports are not advertised to contacts,
|
||||
/// and self-sent messages are not sent there,
|
||||
/// so that we don't cause extra messages to the corresponding inbox,
|
||||
/// but can still receive messages from contacts who don't know our new transport addresses yet.
|
||||
///
|
||||
/// The default is false, but when the user updates from a version that didn't have this flag,
|
||||
/// existing secondary transports are set to unpublished,
|
||||
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||
/// When more transports are added by [`Self::add_or_update_transport()`] or [`Self::add_transport_from_qr`],
|
||||
/// the least recently needed unpublished transport is automatically removed
|
||||
/// if this is necessary in order to stay below the maximum number of allowed relays.
|
||||
/// Also, unpublished transports that are not used to receive any new messages for a time defined by
|
||||
/// [`UNPUBLISHED_TRANSPORT_KEEP_TIME`] are automatically removed.
|
||||
///
|
||||
/// [`UNPUBLISHED_TRANSPORT_KEEP_TIME`]: deltachat::sql::UNPUBLISHED_TRANSPORT_KEEP_TIME
|
||||
async fn set_transport_unpublished(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -2279,7 +2295,7 @@ impl CommandApi {
|
||||
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
|
||||
let blob = message.get_webxdc_blob(&ctx, &path).await?;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use deltachat::calls::{call_state, CallState};
|
||||
use deltachat::calls::{CallState, call_state};
|
||||
use deltachat::context::Context;
|
||||
use deltachat::message::MsgId;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use deltachat::chat::{self, ChatVisibility, get_chat_contacts, get_past_chat_contacts};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
|
||||
@@ -4,7 +4,7 @@ use deltachat::chatlist::get_last_message_for_chat;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chat::{ChatVisibility, get_chat_contacts},
|
||||
chatlist::Chatlist,
|
||||
};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct HttpResponse {
|
||||
|
||||
impl From<CoreHttpResponse> for HttpResponse {
|
||||
fn from(response: CoreHttpResponse) -> Self {
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob);
|
||||
let mimetype = response.mimetype;
|
||||
let encoding = response.encoding;
|
||||
|
||||
@@ -66,10 +66,6 @@ pub struct EnteredLoginParam {
|
||||
/// invalid hostnames.
|
||||
/// Default: Automatic
|
||||
pub certificate_checks: Option<EnteredCertificateChecks>,
|
||||
|
||||
/// If true, login via OAUTH2 (not recommended anymore).
|
||||
/// Default: false
|
||||
pub oauth2: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<dc::TransportListEntry> for TransportListEntry {
|
||||
@@ -100,7 +96,6 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
|
||||
smtp_user: param.smtp.user.into_option(),
|
||||
smtp_password: param.smtp.password.into_option(),
|
||||
certificate_checks: certificate_checks.into_option(),
|
||||
oauth2: param.oauth2.into_option(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +122,6 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
|
||||
password: param.smtp_password.unwrap_or_default(),
|
||||
},
|
||||
certificate_checks: param.certificate_checks.unwrap_or_default().into(),
|
||||
oauth2: param.oauth2.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@ impl From<Qr> for QrObject {
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
@@ -255,6 +256,7 @@ impl From<Qr> for QrObject {
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
@@ -276,6 +278,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
invitenumber,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.54.0-dev"
|
||||
"version": "2.57.0-dev"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "ratelimit"
|
||||
version = "1.0.0"
|
||||
description = "Token bucket implementation"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use anyhow::{Result, bail, ensure};
|
||||
use deltachat::chat::{self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration};
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::constants::*;
|
||||
@@ -319,7 +319,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
info\n\
|
||||
set <configuration-key> [<value>]\n\
|
||||
get <configuration-key>\n\
|
||||
oauth2\n\
|
||||
configure\n\
|
||||
connect\n\
|
||||
disconnect\n\
|
||||
@@ -1226,12 +1225,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"fileinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
|
||||
if let Ok(buf) = read_file(&context, Path::new(arg1)).await {
|
||||
let (width, height) = get_filemeta(&buf)?;
|
||||
println!("width={width}, height={height}");
|
||||
} else {
|
||||
let Ok(buf) = read_file(&context, Path::new(arg1)).await else {
|
||||
bail!("Command failed.");
|
||||
}
|
||||
};
|
||||
let (width, height) = get_filemeta(&buf)?;
|
||||
println!("width={width}, height={height}");
|
||||
}
|
||||
"estimatedeletion" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <seconds> missing");
|
||||
|
||||
@@ -9,14 +9,12 @@ extern crate deltachat;
|
||||
|
||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use anyhow::{Error, bail};
|
||||
use deltachat::EventType;
|
||||
use deltachat::chat::ChatId;
|
||||
use deltachat::config;
|
||||
use deltachat::context::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::EventType;
|
||||
use log::{error, info, warn};
|
||||
use nu_ansi_term::Color;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
@@ -162,11 +160,10 @@ const IMEX_COMMANDS: [&str; 10] = [
|
||||
"stop",
|
||||
];
|
||||
|
||||
const DB_COMMANDS: [&str; 11] = [
|
||||
const DB_COMMANDS: [&str; 10] = [
|
||||
"info",
|
||||
"set",
|
||||
"get",
|
||||
"oauth2",
|
||||
"configure",
|
||||
"connect",
|
||||
"disconnect",
|
||||
@@ -425,19 +422,6 @@ async fn handle_cmd(
|
||||
"configure" => {
|
||||
ctx.configure().await?;
|
||||
}
|
||||
"oauth2" => {
|
||||
if let Some(addr) = ctx.get_config(config::Config::Addr).await? {
|
||||
if let Some(oauth2_url) =
|
||||
get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?
|
||||
{
|
||||
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
|
||||
} else {
|
||||
println!("OAuth2 not available for {addr}.");
|
||||
}
|
||||
} else {
|
||||
println!("oauth2: set addr first.");
|
||||
}
|
||||
}
|
||||
"clear" => {
|
||||
println!("\n\n\n");
|
||||
print!("\x1b[1;1H\x1b[2J");
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
license = "MPL-2.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
|
||||
@@ -133,6 +133,10 @@ class Account:
|
||||
"""Delete a transport."""
|
||||
self._rpc.delete_transport(self.id, addr)
|
||||
|
||||
def set_transport_unpublished(self, addr: str, unpublished: bool = True):
|
||||
"""Unpublish the transport."""
|
||||
self._rpc.set_transport_unpublished(self.id, addr, unpublished)
|
||||
|
||||
@futuremethod
|
||||
def list_transports(self):
|
||||
"""Return the list of all email accounts that are used as a transport in the current profile."""
|
||||
|
||||
@@ -231,14 +231,6 @@ class KeyGenType(IntEnum):
|
||||
RSA4096 = 3
|
||||
|
||||
|
||||
# "Lp" means "login parameters"
|
||||
class LpAuthFlag(IntEnum):
|
||||
"""Authorization flags."""
|
||||
|
||||
OAUTH2 = 0x2
|
||||
NORMAL = 0x4
|
||||
|
||||
|
||||
class MediaQuality(IntEnum):
|
||||
"""Media quality setting."""
|
||||
|
||||
|
||||
@@ -237,10 +237,21 @@ def test_transport_limit(acfactory) -> None:
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
second_addr = account.list_transports()[1]["addr"]
|
||||
account.delete_transport(second_addr)
|
||||
third_addr = account.list_transports()[2]["addr"]
|
||||
|
||||
# test that adding a transport after deleting one works again
|
||||
# test that adding a transport after unpublishing one works again
|
||||
account.set_transport_unpublished(second_addr)
|
||||
account.add_transport_from_qr(qr)
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
# UIs are not expected to delete transports directly,
|
||||
# but we still test that adding a transport
|
||||
# after deleting one instead of unpublishing works.
|
||||
account.delete_transport(third_addr)
|
||||
account.add_transport_from_qr(qr)
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
|
||||
def test_message_info_imap_urls(acfactory) -> None:
|
||||
|
||||
@@ -704,3 +704,25 @@ def test_withdraw_securejoin_qr(acfactory):
|
||||
and "Ignoring RequestWithAuth message because of invalid auth code." in event.msg
|
||||
):
|
||||
break
|
||||
|
||||
|
||||
def test_qr_scan_updates_new_relay_address(acfactory):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
bob_alice_chat = bob.secure_join(alice.get_qr_code())
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
for ac in [alice, bob]:
|
||||
old_addr = ac.get_config("configured_addr")
|
||||
ac.add_transport_from_qr(acfactory.get_account_qr())
|
||||
ac.set_config("configured_addr", ac.list_transports()[1]["addr"])
|
||||
ac.delete_transport(old_addr)
|
||||
|
||||
bob.secure_join(alice.get_qr_code())
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
bob_alice_chat.send_text("hi")
|
||||
snapshot = alice.wait_for_incoming_msg().get_snapshot()
|
||||
assert snapshot.text == "hi"
|
||||
|
||||
@@ -221,6 +221,30 @@ def test_account(acfactory) -> None:
|
||||
alice.stop_io()
|
||||
|
||||
|
||||
def test_mark_fresh_vs_self_mdn(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
bob.set_config("bcc_self", "1")
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob)
|
||||
alice_chat = alice_contact_bob.create_chat()
|
||||
alice_chat.send_text("Hello!")
|
||||
|
||||
event = bob.wait_for_incoming_msg_event()
|
||||
chat_id = event.chat_id
|
||||
msg_id = event.msg_id
|
||||
|
||||
bob_chat = bob.get_chat_by_id(chat_id)
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
bob_chat.accept()
|
||||
bob.mark_seen_messages([message])
|
||||
bob_chat.mark_fresh()
|
||||
assert bob_chat.get_fresh_message_count() == 1
|
||||
alice.wait_for_event(EventType.MSG_READ)
|
||||
alice_chat.send_text("You've read 'Hello!'")
|
||||
bob.wait_for_incoming_msg_event()
|
||||
assert bob_chat.get_fresh_message_count() == 2
|
||||
|
||||
|
||||
def test_chat(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
@@ -356,7 +380,7 @@ def test_receive_imf_failure(acfactory) -> None:
|
||||
snapshot.text == "❌ Failed to receive a message:"
|
||||
" Condition failed: `!context.get_config_bool(Config::SimulateReceiveImfError).await?`."
|
||||
f" Core version {version}."
|
||||
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/."
|
||||
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/"
|
||||
)
|
||||
|
||||
# The failed message doesn't break the IMAP loop.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.54.0-dev"
|
||||
"version": "2.57.0-dev"
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ for (const { folder_name, package_name } of platform_package_names) {
|
||||
|
||||
if (is_local) {
|
||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] =
|
||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc-bindings/typescript")}`;
|
||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc/typescript")}`;
|
||||
} else {
|
||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*";
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use deltachat::constants::DC_VERSION_STR;
|
||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||
use futures_lite::stream::StreamExt;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "deltachat-time"
|
||||
version = "1.0.0"
|
||||
description = "Time-related tools"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
edition = "2018"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
|
||||
let gen = quote! {
|
||||
let r#gen = quote! {
|
||||
impl rusqlite::types::ToSql for #name {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let num = *self as i64;
|
||||
@@ -24,7 +24,7 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
r#gen.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromSql)]
|
||||
@@ -32,7 +32,7 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
|
||||
let gen = quote! {
|
||||
let r#gen = quote! {
|
||||
impl rusqlite::types::FromSql for #name {
|
||||
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let inner = rusqlite::types::FromSql::column_result(col)?;
|
||||
@@ -44,5 +44,5 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
r#gen.into()
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ skip = [
|
||||
{ name = "rustix", version = "0.38.44" },
|
||||
{ name = "rustls-webpki", version = "0.102.8" },
|
||||
{ name = "serdect", version = "0.2.0" },
|
||||
{ name = "snafu-derive", version = "0.8.5" },
|
||||
{ name = "snafu", version = "0.8.5" },
|
||||
{ name = "socket2", version = "0.5.9" },
|
||||
{ name = "spin", version = "0.9.8" },
|
||||
{ name = "strum_macros", version = "0.26.2" },
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
./deltachat-contact-tools
|
||||
./deltachat-ffi
|
||||
./deltachat-jsonrpc
|
||||
./deltachat-jsonrpc-bindings
|
||||
./deltachat-ratelimit
|
||||
./deltachat-repl
|
||||
./deltachat-rpc-client
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "format-flowed"
|
||||
version = "1.0.0"
|
||||
description = "format=flowed support"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
keywords = ["email"]
|
||||
|
||||
@@ -224,8 +224,7 @@ mod tests {
|
||||
fn test_unformat_flowed() {
|
||||
let text = "this is a very long message that should be wrapped using format=flowed and \n\
|
||||
unwrapped on the receiver";
|
||||
let expected =
|
||||
"this is a very long message that should be wrapped using format=flowed and \
|
||||
let expected = "this is a very long message that should be wrapped using format=flowed and \
|
||||
unwrapped on the receiver";
|
||||
assert_eq!(unformat_flowed(text, false), expected);
|
||||
|
||||
@@ -255,8 +254,7 @@ mod tests {
|
||||
assert_eq!(format_flowed_quote(quote), expected);
|
||||
|
||||
let quote = "this is a very long quote that should be wrapped using format=flowed and unwrapped on the receiver";
|
||||
let expected =
|
||||
"> this is a very long quote that should be wrapped using format=flowed and \r\n\
|
||||
let expected = "> this is a very long quote that should be wrapped using format=flowed and \r\n\
|
||||
> unwrapped on the receiver";
|
||||
assert_eq!(format_flowed_quote(quote), expected);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "deltachat-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "2.54.0-dev"
|
||||
version = "2.57.0-dev"
|
||||
license = "MPL-2.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026-06-15
|
||||
2026-07-21
|
||||
@@ -145,9 +145,6 @@ def process_data(data, file):
|
||||
opt = process_opt(data)
|
||||
config_defaults = process_config_defaults(data)
|
||||
|
||||
oauth2 = data.get("oauth2", "")
|
||||
oauth2 = "Some(Oauth2Authorizer::" + camel(oauth2) + ")" if oauth2 != "" else "None"
|
||||
|
||||
provider = ""
|
||||
before_login_hint = cleanstr(data.get("before_login_hint", "") or "")
|
||||
after_login_hint = cleanstr(data.get("after_login_hint", ""))
|
||||
@@ -165,7 +162,6 @@ def process_data(data, file):
|
||||
provider += " server: &[\n" + server + " ],\n"
|
||||
provider += " opt: " + opt + ",\n"
|
||||
provider += " config_defaults: " + config_defaults + ",\n"
|
||||
provider += " oauth2_authorizer: " + oauth2 + ",\n"
|
||||
provider += "};\n\n"
|
||||
else:
|
||||
raise TypeError("SMTP and IMAP must be specified together or left out both")
|
||||
@@ -180,7 +176,7 @@ def process_data(data, file):
|
||||
out_all += "// " + file.name + ": " + comment.strip(", ") + "\n"
|
||||
|
||||
# also add provider with no special things to do -
|
||||
# eg. _not_ supporting oauth2 is also an information and we can skip the mx-lookup in this case
|
||||
# eg. we can skip the mx-lookup in this case
|
||||
out_all += provider
|
||||
out_domains += domains
|
||||
out_ids += ids
|
||||
@@ -212,7 +208,7 @@ if __name__ == "__main__":
|
||||
"use crate::provider::Socket::*;\n"
|
||||
"use crate::provider::UsernamePattern::*;\n"
|
||||
"use crate::provider::{\n"
|
||||
" Config, ConfigDefault, Oauth2Authorizer, Provider, ProviderOptions, Server, Status,\n"
|
||||
" Config, ConfigDefault, Provider, ProviderOptions, Server, Status,\n"
|
||||
"};\n"
|
||||
"use std::collections::HashMap;\n\n"
|
||||
"use std::sync::LazyLock;\n\n"
|
||||
|
||||
@@ -67,14 +67,13 @@ def main():
|
||||
parser.add_argument("newversion")
|
||||
|
||||
json_list = [
|
||||
"deltachat-jsonrpc-bindings/typescript/package.json",
|
||||
"deltachat-jsonrpc/typescript/package.json",
|
||||
"deltachat-rpc-server/npm-package/package.json",
|
||||
]
|
||||
toml_list = [
|
||||
"Cargo.toml",
|
||||
"deltachat-ffi/Cargo.toml",
|
||||
"deltachat-jsonrpc/Cargo.toml",
|
||||
"deltachat-jsonrpc-bindings/Cargo.toml",
|
||||
"deltachat-rpc-server/Cargo.toml",
|
||||
"deltachat-repl/Cargo.toml",
|
||||
"python/pyproject.toml",
|
||||
|
||||
@@ -770,7 +770,7 @@ pub(crate) fn create_fallback_ice_servers() -> Vec<UnresolvedIceServer> {
|
||||
/// because it itself cannot utilize DNS. See
|
||||
/// <https://github.com/deltachat/deltachat-desktop/issues/5447>.
|
||||
pub async fn ice_servers(context: &Context) -> Result<String> {
|
||||
if let Some(ref metadata) = *context.metadata.read().await {
|
||||
if let Some(metadata) = context.metadata.read().await.values().next() {
|
||||
let ice_servers = resolve_ice_servers(context, metadata.ice_servers.clone()).await?;
|
||||
Ok(ice_servers)
|
||||
} else {
|
||||
|
||||
27
src/chat.rs
27
src/chat.rs
@@ -1794,8 +1794,7 @@ impl Chat {
|
||||
}
|
||||
|
||||
let is_bot = context.get_config_bool(Config::Bot).await?;
|
||||
msg.param
|
||||
.set_optional(Param::Bot, Some("1").filter(|_| is_bot));
|
||||
msg.param.set_optional(Param::Bot, is_bot.then_some("1"));
|
||||
|
||||
// Set "In-Reply-To:" to identify the message to which the composed message is a reply.
|
||||
// Set "References:" to identify the "thread" of the conversation.
|
||||
@@ -2926,10 +2925,9 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
||||
|
||||
let now = time();
|
||||
|
||||
if rendered_msg.last_added_location_id.is_some()
|
||||
&& let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await
|
||||
{
|
||||
error!(context, "Failed to set kml sent_timestamp: {err:#}.");
|
||||
if let Some(last_added_location_timestamp) = rendered_msg.last_added_location_timestamp {
|
||||
location::set_kml_sent_timestamp(context, msg.chat_id, last_added_location_timestamp)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if attach_selfavatar && let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await
|
||||
@@ -2971,7 +2969,6 @@ WHERE id=?
|
||||
)
|
||||
.await?;
|
||||
|
||||
let chunk_size = context.get_max_smtp_rcpt_to().await?;
|
||||
let trans_fn = |t: &mut rusqlite::Transaction| {
|
||||
let mut row_ids = Vec::<i64>::new();
|
||||
|
||||
@@ -2981,16 +2978,16 @@ WHERE id=?
|
||||
(),
|
||||
)?;
|
||||
}
|
||||
let mut stmt = t.prepare(
|
||||
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
)?;
|
||||
for recipients_chunk in recipients.chunks(chunk_size) {
|
||||
let recipients_chunk = recipients_chunk.join(" ");
|
||||
if !recipients.is_empty() {
|
||||
let mut stmt = t.prepare(
|
||||
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
)?;
|
||||
let all_recipients = recipients.join(" ");
|
||||
if let Some(pre_msg) = &rendered_pre_msg {
|
||||
let row_id = stmt.execute((
|
||||
&pre_msg.rfc724_mid,
|
||||
&recipients_chunk,
|
||||
&all_recipients,
|
||||
&pre_msg.message,
|
||||
msg.id,
|
||||
))?;
|
||||
@@ -2998,7 +2995,7 @@ WHERE id=?
|
||||
}
|
||||
let row_id = stmt.execute((
|
||||
&rendered_msg.rfc724_mid,
|
||||
&recipients_chunk,
|
||||
&all_recipients,
|
||||
&rendered_msg.message,
|
||||
msg.id,
|
||||
))?;
|
||||
|
||||
@@ -1374,6 +1374,18 @@ async fn test_markfresh_chat() -> Result<()> {
|
||||
assert_eq!(bob_chat_id.get_fresh_msg_cnt(bob).await?, 0);
|
||||
assert_eq!(bob.get_fresh_msgs().await?.len(), 0);
|
||||
|
||||
// Marking a message as seen results to sending an MDN to the contact and self.
|
||||
message::markseen_msgs(bob, vec![bob_msg2.id]).await?;
|
||||
assert_eq!(
|
||||
bob.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id=?",
|
||||
(bob_msg2.from_id,)
|
||||
)
|
||||
.await?,
|
||||
1
|
||||
);
|
||||
|
||||
// bob marks the chat as fresh again, fresh count is 1 again
|
||||
markfresh_chat(bob, bob_chat_id).await?;
|
||||
let bob_msg1 = Message::load_from_db(bob, bob_msg1.id).await?;
|
||||
|
||||
108
src/chatlist.rs
108
src/chatlist.rs
@@ -115,35 +115,40 @@ impl Chatlist {
|
||||
ChatId::new(0)
|
||||
};
|
||||
|
||||
// select with left join and minimum:
|
||||
//
|
||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||
// which would refer the outer select and take a lot of time
|
||||
// - `GROUP BY` is needed several messages may have the same
|
||||
// timestamp
|
||||
// - the list starts with the newest chats
|
||||
macro_rules! last_visible_msg_id_in {
|
||||
($chat_id:expr) => {
|
||||
concat!(
|
||||
"
|
||||
(SELECT id FROM msgs WHERE
|
||||
-- state=`OutDraft`.
|
||||
state=19 AND hidden=1 AND chat_id=",
|
||||
$chat_id,
|
||||
" OR
|
||||
-- `InFresh`...`OutDelivered` inclusive, except `OutDraft`.
|
||||
state IN (10,13,16,20,24,26) AND hidden=0 AND chat_id=",
|
||||
$chat_id,
|
||||
"
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1)"
|
||||
)
|
||||
};
|
||||
}
|
||||
// The list starts with the newest chats.
|
||||
//
|
||||
// The query shows messages from blocked contacts in
|
||||
// groups. Otherwise it would be hard to follow conversations.
|
||||
let ids = if let Some(query_contact_id) = query_contact_id {
|
||||
// show chats shared with a given contact
|
||||
context.sql.query_map_vec(
|
||||
context.sql.query_map_vec(concat!(
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.id=(
|
||||
SELECT id
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?1)
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1)
|
||||
AND m.id=", last_visible_msg_id_in!("c.id"), "
|
||||
WHERE c.id>9
|
||||
AND c.blocked!=1
|
||||
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2 AND add_timestamp >= remove_timestamp)
|
||||
GROUP BY c.id
|
||||
ORDER BY c.archived=?3 DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
|
||||
(MessageState::OutDraft, query_contact_id, ChatVisibility::Pinned),
|
||||
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=? AND add_timestamp >= remove_timestamp)
|
||||
ORDER BY c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
|
||||
(query_contact_id, ChatVisibility::Pinned),
|
||||
process_row,
|
||||
).await?
|
||||
} else if flag_archived_only {
|
||||
@@ -154,22 +159,21 @@ impl Chatlist {
|
||||
context
|
||||
.sql
|
||||
.query_map_vec(
|
||||
"SELECT c.id, m.id
|
||||
concat!(
|
||||
"
|
||||
SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.id=(
|
||||
SELECT id
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?)
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1)
|
||||
AND m.id=",
|
||||
last_visible_msg_id_in!("c.id"),
|
||||
"
|
||||
WHERE c.id>9
|
||||
AND c.blocked!=1
|
||||
AND c.archived=1
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
|
||||
(MessageState::OutDraft,),
|
||||
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"
|
||||
),
|
||||
(),
|
||||
process_row,
|
||||
)
|
||||
.await?
|
||||
@@ -188,24 +192,18 @@ impl Chatlist {
|
||||
let str_like_cmd = format!("%{}%", query.to_lowercase());
|
||||
context
|
||||
.sql
|
||||
.query_map_vec(
|
||||
.query_map_vec(concat!(
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.id=(
|
||||
SELECT id
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?1)
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1)
|
||||
WHERE c.id>9 AND c.id!=?2
|
||||
AND m.id=", last_visible_msg_id_in!("c.id"), "
|
||||
WHERE c.id>9 AND c.id!=?
|
||||
AND c.blocked!=1
|
||||
AND IFNULL(c.name_normalized,c.name) LIKE ?3
|
||||
AND (NOT ?4 OR EXISTS (SELECT 1 FROM msgs m WHERE m.chat_id = c.id AND m.state == ?5 AND hidden=0))
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
|
||||
(MessageState::OutDraft, skip_id, str_like_cmd, only_unread, MessageState::InFresh),
|
||||
AND IFNULL(c.name_normalized,c.name) LIKE ?
|
||||
AND (NOT ? OR EXISTS (SELECT 1 FROM msgs m WHERE m.chat_id = c.id AND m.state == ? AND hidden=0))
|
||||
ORDER BY IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
|
||||
(skip_id, str_like_cmd, only_unread, MessageState::InFresh),
|
||||
process_row,
|
||||
)
|
||||
.await?
|
||||
@@ -237,25 +235,19 @@ impl Chatlist {
|
||||
})
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
};
|
||||
context.sql.query_map(
|
||||
context.sql.query_map(concat!(
|
||||
"SELECT c.id, c.type, c.param, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.id=(
|
||||
SELECT id
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?)
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1)
|
||||
AND m.id=", last_visible_msg_id_in!("c.id"), "
|
||||
WHERE c.id>9 AND c.id!=?
|
||||
AND c.blocked=0
|
||||
AND NOT c.archived=?
|
||||
AND (c.type!=? OR c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=? AND add_timestamp >= remove_timestamp))
|
||||
GROUP BY c.id
|
||||
ORDER BY c.id=? DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
|
||||
ORDER BY c.id=? DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
|
||||
(
|
||||
MessageState::OutDraft, skip_id, ChatVisibility::Archived,
|
||||
skip_id, ChatVisibility::Archived,
|
||||
Chattype::Group, ContactId::SELF,
|
||||
sort_id_up, ChatVisibility::Pinned,
|
||||
),
|
||||
@@ -264,23 +256,17 @@ impl Chatlist {
|
||||
).await?
|
||||
} else {
|
||||
// show normal chatlist
|
||||
context.sql.query_map_vec(
|
||||
context.sql.query_map_vec(concat!(
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
AND m.id=(
|
||||
SELECT id
|
||||
FROM msgs
|
||||
WHERE chat_id=c.id
|
||||
AND (hidden=0 OR state=?)
|
||||
ORDER BY timestamp DESC, id DESC LIMIT 1)
|
||||
AND m.id=", last_visible_msg_id_in!("c.id"), "
|
||||
WHERE c.id>9 AND c.id!=?
|
||||
AND (c.blocked=0 OR c.blocked=2)
|
||||
AND NOT c.archived=?
|
||||
GROUP BY c.id
|
||||
ORDER BY c.id=0 DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC;",
|
||||
(MessageState::OutDraft, skip_id, ChatVisibility::Archived, ChatVisibility::Pinned),
|
||||
ORDER BY c.id=0 DESC, c.archived=? DESC, IFNULL(NULLIF(m.timestamp,0),c.created_timestamp) DESC, m.id DESC"),
|
||||
(skip_id, ChatVisibility::Archived, ChatVisibility::Pinned),
|
||||
process_row,
|
||||
).await?
|
||||
};
|
||||
|
||||
@@ -17,10 +17,9 @@ use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
use crate::log::LogExt;
|
||||
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
||||
use crate::provider::Provider;
|
||||
use crate::sync::{self, Sync::*, SyncData};
|
||||
use crate::tools::{get_abs_path, time};
|
||||
use crate::transport::{ConfiguredLoginParam, add_pseudo_transport, send_sync_transports};
|
||||
use crate::transport::{add_pseudo_transport, send_sync_transports};
|
||||
use crate::{constants, stats};
|
||||
|
||||
/// The available configuration keys.
|
||||
@@ -118,15 +117,6 @@ pub enum Config {
|
||||
/// SMTP server security (e.g. TLS, STARTTLS).
|
||||
SendSecurity,
|
||||
|
||||
/// Deprecated(2026-04).
|
||||
/// Use EnteredLoginParam and add_transport{from_qr}()/list_transports() instead.
|
||||
///
|
||||
/// Whether to use OAuth 2.
|
||||
///
|
||||
/// Historically contained other bitflags, which are now deprecated.
|
||||
/// Should not be extended in the future, create new config keys instead.
|
||||
ServerFlags,
|
||||
|
||||
/// True if proxy is enabled.
|
||||
///
|
||||
/// Can be used to disable proxy without erasing known URLs.
|
||||
@@ -298,12 +288,6 @@ pub enum Config {
|
||||
/// Configured SMTP server password.
|
||||
ConfiguredSendPw,
|
||||
|
||||
/// Deprecated(2026-04).
|
||||
/// Use ConfiguredLoginParam and add_transport{from_qr}()/list_transports() instead.
|
||||
///
|
||||
/// Whether OAuth 2 is used with configured provider.
|
||||
ConfiguredServerFlags,
|
||||
|
||||
/// Configured folder for incoming messages.
|
||||
ConfiguredInboxFolder,
|
||||
|
||||
@@ -319,6 +303,12 @@ pub enum Config {
|
||||
/// True if account is configured.
|
||||
Configured,
|
||||
|
||||
/// Deprecated, we are trying to get rid of this global setting.
|
||||
/// It is possible to configure a profile with both chatmail relays
|
||||
/// and classical email servers.
|
||||
///
|
||||
/// Most usages in UIs can be replaced by `force_encryption`.
|
||||
///
|
||||
/// True if account is a chatmail account.
|
||||
IsChatmail,
|
||||
|
||||
@@ -455,12 +445,6 @@ pub enum Config {
|
||||
/// Return an error from `receive_imf_inner()`. For tests.
|
||||
SimulateReceiveImfError,
|
||||
|
||||
/// Enable composing emails with Header Protection as defined in
|
||||
/// <https://www.rfc-editor.org/rfc/rfc9788.html> "Header Protection for Cryptographically
|
||||
/// Protected Email".
|
||||
#[strum(props(default = "1"))]
|
||||
StdHeaderProtectionComposing,
|
||||
|
||||
/// Who can call me.
|
||||
///
|
||||
/// The options are from the `WhoCanCallMe` enum.
|
||||
@@ -629,16 +613,6 @@ impl Context {
|
||||
self.get_config_bool(Config::MdnsEnabled).await
|
||||
}
|
||||
|
||||
/// Gets the configured provider.
|
||||
///
|
||||
/// The provider is determined by the current primary transport.
|
||||
pub async fn get_configured_provider(&self) -> Result<Option<&'static Provider>> {
|
||||
let provider = ConfiguredLoginParam::load(self)
|
||||
.await?
|
||||
.and_then(|(_transport_id, param)| param.provider);
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
/// Gets configured "delete_device_after" value.
|
||||
///
|
||||
/// `None` means never delete the message, `Some(x)` means delete
|
||||
|
||||
222
src/configure.rs
222
src/configure.rs
@@ -32,7 +32,6 @@ pub use crate::login_param::EnteredLoginParam;
|
||||
use crate::login_param::{EnteredCertificateChecks, TransportListEntry};
|
||||
use crate::message::Message;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::oauth2::get_oauth2_addr;
|
||||
use crate::provider::{Protocol, Provider, Socket, UsernamePattern};
|
||||
use crate::qr::{login_param_from_account_qr, login_param_from_login_qr};
|
||||
use crate::smtp::Smtp;
|
||||
@@ -45,9 +44,10 @@ use crate::transport::{
|
||||
use crate::{EventType, stock_str};
|
||||
use crate::{chat, provider};
|
||||
|
||||
/// Maximum number of relays
|
||||
/// see <https://github.com/chatmail/core/issues/7608>
|
||||
pub(crate) const MAX_TRANSPORT_RELAYS: usize = 5;
|
||||
/// Maximum number of relays.
|
||||
///
|
||||
/// See <https://github.com/chatmail/core/issues/7608>.
|
||||
pub(crate) const MAX_RELAYS: usize = 5;
|
||||
|
||||
/// Hard-coded candidates for default relays.
|
||||
/// In the future, we want to use it during onboarding;
|
||||
@@ -230,8 +230,9 @@ impl Context {
|
||||
self.sql.count("SELECT COUNT(*) FROM transports", ()).await
|
||||
}
|
||||
|
||||
/// Removes the transport with the specified email address
|
||||
/// (i.e. [EnteredLoginParam::addr]).
|
||||
/// Immediately deletes a transport, potentially causing messages not to arrive.
|
||||
/// This must ONLY be used internally and by the automated tests.
|
||||
/// UI implementations must use [`Self::set_transport_unpublished`] instead.
|
||||
pub async fn delete_transport(&self, addr: &str) -> Result<()> {
|
||||
let now = time();
|
||||
let removed_transport_id = self
|
||||
@@ -282,15 +283,20 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Change whether the transport is unpublished.
|
||||
/// UIs should call this function when the user clicks on "Remove".
|
||||
/// Core will keep listening on this transport for some time,
|
||||
/// and automatically remove it once it is no longer needed.
|
||||
///
|
||||
/// Unpublished transports are not advertised to contacts,
|
||||
/// and self-sent messages are not sent there,
|
||||
/// so that we don't cause extra messages to the corresponding inbox,
|
||||
/// but can still receive messages from contacts who don't know our new transport addresses yet.
|
||||
///
|
||||
/// The default is false, but when the user updates from a version that didn't have this flag,
|
||||
/// existing secondary transports are set to unpublished,
|
||||
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||
/// When more transports are added by [`Self::add_or_update_transport()`] or [`Self::add_transport_from_qr`],
|
||||
/// the least recently needed unpublished transport is automatically removed
|
||||
/// if this is necessary in order to stay below the maximum number of allowed relays.
|
||||
/// Also, unpublished transports that are not used to receive any new messages for a time defined by
|
||||
/// `UNPUBLISHED_TRANSPORT_KEEP_TIME` are automatically removed.
|
||||
pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> {
|
||||
self.sql
|
||||
.transaction(|trans| {
|
||||
@@ -322,25 +328,15 @@ impl Context {
|
||||
async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
|
||||
info!(self, "Configure ...");
|
||||
|
||||
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
|
||||
if old_addr.is_some()
|
||||
&& !self
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM transports WHERE addr=?",
|
||||
(¶m.addr,),
|
||||
)
|
||||
.await?
|
||||
&& self
|
||||
.sql
|
||||
.count("SELECT COUNT(*) FROM transports", ())
|
||||
.await?
|
||||
>= MAX_TRANSPORT_RELAYS
|
||||
{
|
||||
bail!(
|
||||
"You have reached the maximum number of relays ({}).",
|
||||
MAX_TRANSPORT_RELAYS
|
||||
if !self
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM transports WHERE addr=?",
|
||||
(¶m.addr,),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
self.try_make_space_for_new_relay().await?;
|
||||
}
|
||||
|
||||
let provider = match configure(self, param).await {
|
||||
@@ -364,6 +360,42 @@ impl Context {
|
||||
on_configure_completed(self, provider).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function is called before adding a new relay.
|
||||
/// If the maximum number of relays ([`MAX_RELAYS`]) is already reached,
|
||||
/// then it tries to make space by removing an unpublished relay.
|
||||
/// If there are multiple unpublished relays,
|
||||
/// the one that hasn't received a message for longest is removed.
|
||||
/// If there are no unpublished relays, an error is returned.
|
||||
///
|
||||
/// Note that eviction happens before we know that a new relay works,
|
||||
/// which is a trade-off we made in favor of implementation complexity.
|
||||
async fn try_make_space_for_new_relay(&self) -> Result<()> {
|
||||
if self.count_transports().await? >= MAX_RELAYS {
|
||||
// Try to automatically remove the unpublished transport that wasn't used for the longest time:
|
||||
if let Some(addr) = self
|
||||
.sql
|
||||
.query_get_value::<String>(
|
||||
"SELECT addr FROM transports WHERE is_published=0
|
||||
ORDER BY last_rcvd_timestamp, add_timestamp LIMIT 1",
|
||||
(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
info!(
|
||||
self,
|
||||
"Auto-deleting relay {addr} to make space for new relay."
|
||||
);
|
||||
self.delete_transport(&addr).await?;
|
||||
}
|
||||
|
||||
if self.count_transports().await? >= MAX_RELAYS {
|
||||
// Apparently, all the transports are published
|
||||
bail!("You have reached the maximum number of relays ({MAX_RELAYS})");
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_configure_completed(
|
||||
@@ -418,24 +450,7 @@ async fn get_configured_param(
|
||||
param.smtp.password.clone()
|
||||
};
|
||||
|
||||
let mut addr = param.addr.clone();
|
||||
if param.oauth2 {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
// if get_oauth2_addr() is not available in the oauth2 implementation, just use the given one.
|
||||
progress!(ctx, 10);
|
||||
if let Some(oauth2_addr) = get_oauth2_addr(ctx, ¶m.addr, ¶m.imap.password)
|
||||
.await?
|
||||
.and_then(|e| e.parse().ok())
|
||||
{
|
||||
info!(ctx, "Authorized address is {}", oauth2_addr);
|
||||
addr = oauth2_addr;
|
||||
ctx.sql
|
||||
.set_raw_config("addr", Some(param.addr.as_str()))
|
||||
.await?;
|
||||
}
|
||||
progress!(ctx, 20);
|
||||
}
|
||||
// no oauth? - just continue it's no error
|
||||
let addr = param.addr.clone();
|
||||
|
||||
let parsed = EmailAddress::new(¶m.addr).context("Bad email-address")?;
|
||||
let param_domain = parsed.domain;
|
||||
@@ -585,7 +600,6 @@ async fn get_configured_param(
|
||||
ConfiguredCertificateChecks::AcceptInvalidCertificates
|
||||
}
|
||||
},
|
||||
oauth2: param.oauth2,
|
||||
};
|
||||
Ok(configured_login_param)
|
||||
}
|
||||
@@ -616,7 +630,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
|
||||
&proxy_config2,
|
||||
&smtp_addr,
|
||||
strict_tls,
|
||||
configured_param.oauth2,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -721,12 +734,12 @@ async fn get_autoconfig(
|
||||
}
|
||||
progress!(ctx, 300);
|
||||
|
||||
// `?emailaddress=` query string is excluded on purpose.
|
||||
// It is not part of the URL according to <https://datatracker.ietf.org/doc/draft-ietf-mailmaint-autoconfig/06/>.
|
||||
// Related discussion confirming this is at <https://github.com/benbucksch/autoconfig-spec/issues/17>.
|
||||
if let Ok(res) = moz_autoconfigure(
|
||||
ctx,
|
||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see <https://releases.mozilla.org/pub/thunderbird/>, which makes some sense
|
||||
&format!(
|
||||
"https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
|
||||
),
|
||||
&format!("https://{param_domain}/.well-known/autoconfig/mail/config-v1.1.xml"),
|
||||
¶m.addr,
|
||||
accept_invalid_certificates,
|
||||
)
|
||||
@@ -810,10 +823,14 @@ pub enum Error {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tools::SystemTime;
|
||||
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::login_param::EnteredImapLoginParam;
|
||||
use crate::sql::update_transport_last_rcvd_timestamp;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::transport::add_pseudo_transport;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_panic_on_bad_credentials() {
|
||||
@@ -844,4 +861,107 @@ mod tests {
|
||||
assert_eq!(configured_param.smtp_user, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_try_make_place_for_new_relay() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// Setting ConfiguredAddr on an unconfigured account creates a pseudo primary transport
|
||||
t.set_config(Config::ConfiguredAddr, Some("primary@example.org"))
|
||||
.await?;
|
||||
|
||||
// Test that try_make_place_for_new_relay() doesn't do anything when we're below the limit
|
||||
assert_eq!(t.count_transports().await?, 1);
|
||||
t.try_make_space_for_new_relay().await?;
|
||||
assert_eq!(t.count_transports().await?, 1);
|
||||
|
||||
for i in 0..(MAX_RELAYS - 2) {
|
||||
add_pseudo_transport(&t, &format!("transport{i}@example.org")).await?;
|
||||
}
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
|
||||
t.try_make_space_for_new_relay().await?;
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
|
||||
|
||||
// Test that try_make_place_for_new_relay() removes the unpublished transport
|
||||
// when we're at the limit
|
||||
add_pseudo_transport(&t, "unpublished@example.org").await?;
|
||||
t.set_transport_unpublished("unpublished@example.org", true)
|
||||
.await?;
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS);
|
||||
t.try_make_space_for_new_relay().await?;
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM transports WHERE addr=?",
|
||||
("unpublished@example.org",),
|
||||
)
|
||||
.await?,
|
||||
false
|
||||
);
|
||||
|
||||
// Test that if there are multiple unpublished relays,
|
||||
// the one that was used least recently is removed
|
||||
t.set_transport_unpublished("transport0@example.org", true)
|
||||
.await?;
|
||||
add_pseudo_transport(&t, "other_unpublished@example.org").await?;
|
||||
t.set_transport_unpublished("other_unpublished@example.org", true)
|
||||
.await?;
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS);
|
||||
|
||||
let transport0_id: u32 = t
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT id FROM transports WHERE addr=?",
|
||||
("transport0@example.org",),
|
||||
)
|
||||
.await?
|
||||
.unwrap();
|
||||
let other_unpublished_id: u32 = t
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT id FROM transports WHERE addr=?",
|
||||
("other_unpublished@example.org",),
|
||||
)
|
||||
.await?
|
||||
.unwrap();
|
||||
|
||||
update_transport_last_rcvd_timestamp(&t, transport0_id).await?;
|
||||
SystemTime::shift(std::time::Duration::from_secs(10));
|
||||
update_transport_last_rcvd_timestamp(&t, other_unpublished_id).await?;
|
||||
|
||||
// Test that try_make_place_for_new_relay()
|
||||
// removes the relay with the oldest last_rcvd_timestamp
|
||||
t.try_make_space_for_new_relay().await?;
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS - 1);
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM transports WHERE addr=?",
|
||||
("transport0@example.org",),
|
||||
)
|
||||
.await?,
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
t.sql
|
||||
.exists(
|
||||
"SELECT COUNT(*) FROM transports WHERE addr=?",
|
||||
("other_unpublished@example.org",),
|
||||
)
|
||||
.await?,
|
||||
true
|
||||
);
|
||||
|
||||
// Test that try_make_place_for_new_relay() fails
|
||||
// if there are MAX_RELAYS published transports
|
||||
add_pseudo_transport(&t, "published_extra@example.org").await?;
|
||||
t.set_transport_unpublished("other_unpublished@example.org", false)
|
||||
.await?;
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS);
|
||||
assert!(t.try_make_space_for_new_relay().await.is_err());
|
||||
assert_eq!(t.count_transports().await?, MAX_RELAYS);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use std::io::BufRead;
|
||||
use std::str::FromStr;
|
||||
|
||||
use quick_xml::XmlVersion;
|
||||
use quick_xml::events::{BytesStart, Event};
|
||||
|
||||
use super::{Error, ServerParams};
|
||||
@@ -71,7 +72,7 @@ fn parse_server<B: BufRead>(
|
||||
})
|
||||
})
|
||||
.map(|typ| {
|
||||
typ.decode_and_unescape_value(reader.decoder())
|
||||
typ.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
|
||||
.unwrap_or_default()
|
||||
.to_lowercase()
|
||||
})
|
||||
@@ -101,7 +102,11 @@ fn parse_server<B: BufRead>(
|
||||
}
|
||||
}
|
||||
Event::Text(ref event) => {
|
||||
let val = event.xml_content().unwrap_or_default().trim().to_owned();
|
||||
let val = event
|
||||
.xml_content(XmlVersion::Implicit1_0)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_owned();
|
||||
|
||||
match tag_config {
|
||||
MozConfigTag::Hostname => hostname = Some(val),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use std::io::BufRead;
|
||||
|
||||
use quick_xml::XmlVersion;
|
||||
use quick_xml::events::Event;
|
||||
|
||||
use super::{Error, ServerParams};
|
||||
@@ -79,7 +80,7 @@ fn parse_protocol<B: BufRead>(
|
||||
}
|
||||
}
|
||||
Event::Text(ref e) => {
|
||||
let val = e.xml_content().unwrap_or_default();
|
||||
let val = e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
match tag.as_str() {
|
||||
@@ -123,7 +124,7 @@ fn parse_redirecturl<B: BufRead>(
|
||||
let mut buf = Vec::new();
|
||||
match reader.read_event_into(&mut buf)? {
|
||||
Event::Text(ref e) => {
|
||||
let val = e.xml_content().unwrap_or_default();
|
||||
let val = e.xml_content(XmlVersion::Implicit1_0).unwrap_or_default();
|
||||
Ok(val.trim().to_string())
|
||||
}
|
||||
_ => Ok("".to_string()),
|
||||
|
||||
@@ -162,25 +162,6 @@ pub const DC_DESIRED_TEXT_LINE_LEN: usize = 100;
|
||||
/// `char`s), not Unicode Grapheme Clusters.
|
||||
pub const DC_DESIRED_TEXT_LEN: usize = DC_DESIRED_TEXT_LINE_LEN * DC_DESIRED_TEXT_LINES;
|
||||
|
||||
// Flags for configuring IMAP and SMTP servers.
|
||||
// These flags are optional
|
||||
// and may be set together with the username, password etc.
|
||||
// via dc_set_config() using the key "server_flags".
|
||||
|
||||
/// Force OAuth2 authorization.
|
||||
///
|
||||
/// This flag does not skip automatic configuration.
|
||||
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
||||
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
|
||||
|
||||
/// Force NORMAL authorization, this is the default.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
|
||||
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_AUTH_FLAGS: i32 = DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL;
|
||||
|
||||
// max. weight of images to send w/o recoding
|
||||
pub const BALANCED_IMAGE_BYTES: usize = 500_000;
|
||||
pub const WORSE_IMAGE_BYTES: usize = 130_000;
|
||||
@@ -199,13 +180,10 @@ pub const WORSE_IMAGE_SIZE: u32 = 640;
|
||||
/// usage by UIs.
|
||||
pub const MAX_RCVD_IMAGE_PIXELS: u32 = 50_000_000;
|
||||
|
||||
// If more recipients are needed in SMTP's `RCPT TO:` header, the recipient list is split into
|
||||
// chunks. This does not affect MIME's `To:` header. Can be overwritten by setting
|
||||
// `max_smtp_rcpt_to` in the provider db.
|
||||
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;
|
||||
// Fallback for the maximum number of recipients in SMTP's `RCPT TO:`;
|
||||
// recipient lists exceeding the limit are sent in chunks.
|
||||
// Relays typically advertise their limit via IMAP METADATA.
|
||||
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: u32 = 50;
|
||||
|
||||
/// 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
|
||||
|
||||
@@ -231,8 +231,6 @@ pub struct InnerContext {
|
||||
/// This is a global mutex-like state for operations which should be modal in the
|
||||
/// clients.
|
||||
running_state: RwLock<RunningState>,
|
||||
/// Mutex to enforce only a single running oauth2 is running.
|
||||
pub(crate) oauth2_mutex: Mutex<()>,
|
||||
/// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
|
||||
pub(crate) wrong_pw_warning_mutex: Mutex<()>,
|
||||
/// Mutex to prevent running housekeeping from multiple threads at once.
|
||||
@@ -266,8 +264,8 @@ pub struct InnerContext {
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc2971>
|
||||
pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
|
||||
|
||||
/// IMAP METADATA.
|
||||
pub(crate) metadata: RwLock<Option<ServerMetadata>>,
|
||||
/// IMAP METADATA, per transport id.
|
||||
pub(crate) metadata: RwLock<BTreeMap<u32, ServerMetadata>>,
|
||||
|
||||
/// ID for this `Context` in the current process.
|
||||
///
|
||||
@@ -484,7 +482,6 @@ impl Context {
|
||||
blobdir,
|
||||
running_state: RwLock::new(Default::default()),
|
||||
sql: Sql::new(dbfile),
|
||||
oauth2_mutex: Mutex::new(()),
|
||||
wrong_pw_warning_mutex: Mutex::new(()),
|
||||
housekeeping_mutex: Mutex::new(()),
|
||||
fetch_msgs_mutex: Mutex::new(()),
|
||||
@@ -495,7 +492,7 @@ impl Context {
|
||||
quota: RwLock::new(BTreeMap::new()),
|
||||
new_msgs_notify,
|
||||
server_id: RwLock::new(None),
|
||||
metadata: RwLock::new(None),
|
||||
metadata: RwLock::new(BTreeMap::new()),
|
||||
creation_time: tools::Time::now(),
|
||||
last_error: parking_lot::RwLock::new("".to_string()),
|
||||
migration_error: parking_lot::RwLock::new(None),
|
||||
@@ -566,25 +563,33 @@ impl Context {
|
||||
self.scheduler.maybe_network().await;
|
||||
}
|
||||
|
||||
/// Deprecated, we are trying to get rid of this global setting.
|
||||
/// It is possible to configure a profile with both chatmail relays
|
||||
/// and classical email servers.
|
||||
///
|
||||
/// Returns true if an account is on a chatmail server.
|
||||
pub async fn is_chatmail(&self) -> Result<bool> {
|
||||
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?
|
||||
/// Returns maximum number of recipients a single email can be sent to.
|
||||
pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<u32> {
|
||||
let Some((transport_id, param)) = ConfiguredLoginParam::load(self).await? else {
|
||||
bail!("Not configured");
|
||||
};
|
||||
let metadata_limit = self
|
||||
.metadata
|
||||
.read()
|
||||
.await
|
||||
.get(&transport_id)
|
||||
.and_then(|metadata| metadata.max_smtp_rcpt_to);
|
||||
if let Some(limit) = metadata_limit {
|
||||
return Ok(limit);
|
||||
}
|
||||
let val = param
|
||||
.provider
|
||||
.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,
|
||||
);
|
||||
.map_or(constants::DEFAULT_MAX_SMTP_RCPT_TO, u32::from);
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -914,7 +919,7 @@ impl Context {
|
||||
.unwrap_or_else(|| "<unset>".to_string()),
|
||||
);
|
||||
|
||||
if let Some(metadata) = &*self.metadata.read().await {
|
||||
if let Some(metadata) = self.metadata.read().await.values().next() {
|
||||
if let Some(comment) = &metadata.comment {
|
||||
res.insert("imap_server_comment", format!("{comment:?}"));
|
||||
}
|
||||
|
||||
@@ -285,7 +285,6 @@ async fn test_get_info_completeness() {
|
||||
"send_pw",
|
||||
"send_port",
|
||||
"send_security",
|
||||
"server_flags",
|
||||
"skip_start_messages",
|
||||
"proxy_url", // May contain passwords, don't leak it to the logs.
|
||||
"socks5_enabled", // SOCKS5 options are deprecated.
|
||||
|
||||
@@ -6,13 +6,16 @@ use std::io::Cursor;
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use mailparse::ParsedMail;
|
||||
use pgp::composed::DecryptionOptions;
|
||||
use pgp::composed::Esk;
|
||||
use pgp::composed::Message;
|
||||
use pgp::composed::PlainSessionKey;
|
||||
use pgp::composed::SignedSecretKey;
|
||||
use pgp::composed::TheRing;
|
||||
use pgp::composed::decrypt_session_key_with_password;
|
||||
use pgp::packet::SymKeyEncryptedSessionKey;
|
||||
use pgp::types::Password;
|
||||
use pgp::types::Seipdv1ReadMode;
|
||||
use pgp::types::StringToKey;
|
||||
|
||||
use crate::chat::ChatId;
|
||||
@@ -48,6 +51,15 @@ pub(crate) async fn decrypt(
|
||||
};
|
||||
let expected_sender_fingerprint: Option<String>;
|
||||
|
||||
let abort_early = true;
|
||||
|
||||
// Use streaming mode for SEIPDv1 decryption to save memory.
|
||||
// This was the default in rPGP 0.19.0
|
||||
// and requires explicitly changing the mode in rPGP 0.20.0.
|
||||
// SEPIDv2 is decrypted in streaming mode in any case.
|
||||
let decrypt_options =
|
||||
DecryptionOptions::new().set_seipdv1_read_mode(Seipdv1ReadMode::Streaming);
|
||||
|
||||
let plain = if let Message::Encrypted { esk, .. } = &*msg
|
||||
// We only allow one ESK for symmetrically encrypted messages
|
||||
// to avoid dealing with messages that are encrypted to multiple symmetric keys
|
||||
@@ -61,9 +73,15 @@ pub(crate) async fn decrypt(
|
||||
expected_sender_fingerprint = fingerprint;
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<Message<'_>> {
|
||||
let plain = msg
|
||||
.decrypt_with_session_key(psk)
|
||||
.context("decrypt_with_session_key")?;
|
||||
let ring = TheRing {
|
||||
session_keys: vec![psk],
|
||||
decrypt_options,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (plain, _ring_result) = msg
|
||||
.decrypt_the_ring(ring, abort_early)
|
||||
.context("decrypt_the_ring")?;
|
||||
|
||||
let plain: Message<'static> = plain.decompress()?;
|
||||
Ok(plain)
|
||||
@@ -75,11 +93,15 @@ pub(crate) async fn decrypt(
|
||||
expected_sender_fingerprint = None;
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<Message<'_>> {
|
||||
let empty_pw = Password::empty();
|
||||
let secret_keys: Vec<&SignedSecretKey> = secret_keys.iter().collect();
|
||||
let plain = msg
|
||||
.decrypt_with_keys(vec![&empty_pw], secret_keys)
|
||||
.context("decrypt_with_keys")?;
|
||||
let ring = TheRing {
|
||||
secret_keys,
|
||||
decrypt_options,
|
||||
..Default::default()
|
||||
};
|
||||
let (plain, _ring_result) = msg
|
||||
.decrypt_the_ring(ring, abort_early)
|
||||
.context("decrypt_the_ring")?;
|
||||
|
||||
let plain: Message<'static> = plain.decompress()?;
|
||||
Ok(plain)
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::io::BufRead;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use quick_xml::{
|
||||
Reader,
|
||||
Reader, XmlVersion,
|
||||
errors::Error as QuickXmlError,
|
||||
events::{BytesEnd, BytesStart, BytesText},
|
||||
};
|
||||
@@ -327,7 +327,7 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
|
||||
})
|
||||
{
|
||||
let href = href
|
||||
.decode_and_unescape_value(reader.decoder())
|
||||
.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
|
||||
@@ -374,7 +374,7 @@ fn maybe_push_tag(
|
||||
fn tag_contains_attr(event: &BytesStart, reader: &Reader<impl BufRead>, name: &str) -> bool {
|
||||
event.attributes().any(|r| {
|
||||
r.map(|a| {
|
||||
a.decode_and_unescape_value(reader.decoder())
|
||||
a.decoded_and_normalized_value(XmlVersion::Implicit1_0, reader.decoder())
|
||||
.map(|v| v == name)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
|
||||
@@ -132,7 +132,7 @@ impl Message {
|
||||
}
|
||||
|
||||
/// Actually downloads a message partially downloaded before if the message is available on the
|
||||
/// session transport, in which case returns `Some`. If the message is available on another
|
||||
/// session transport, in which case returns `Some`. If the message is not available on this
|
||||
/// transport, returns `None`.
|
||||
///
|
||||
/// Most messages are downloaded automatically on fetch instead.
|
||||
@@ -145,29 +145,24 @@ pub(crate) async fn download_msg(
|
||||
let row = context
|
||||
.sql
|
||||
.query_row_optional(
|
||||
"SELECT uid, folder, transport_id FROM imap
|
||||
"SELECT uid, folder FROM imap
|
||||
WHERE rfc724_mid=? AND target!=''
|
||||
ORDER BY transport_id=? DESC LIMIT 1",
|
||||
AND transport_id=?
|
||||
LIMIT 1",
|
||||
(&rfc724_mid, transport_id),
|
||||
|row| {
|
||||
let server_uid: u32 = row.get(0)?;
|
||||
let server_folder: String = row.get(1)?;
|
||||
let msg_transport_id: u32 = row.get(2)?;
|
||||
Ok((server_uid, server_folder, msg_transport_id))
|
||||
Ok((server_uid, server_folder))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let Some((server_uid, server_folder, msg_transport_id)) = row else {
|
||||
// No IMAP record found, we don't know the UID and folder.
|
||||
let Some((server_uid, server_folder)) = row else {
|
||||
// No IMAP record found, the message is not available on this transport.
|
||||
delete_from_available_post_msgs(context, &rfc724_mid).await?;
|
||||
return Err(anyhow!(
|
||||
"IMAP location for {rfc724_mid:?} post-message is unknown"
|
||||
));
|
||||
};
|
||||
if msg_transport_id != transport_id {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
Box::pin(session.fetch_single_msg(context, &server_folder, server_uid, rfc724_mid)).await?;
|
||||
|
||||
let bcc_self = context.get_config_bool(Config::BccSelf).await?;
|
||||
@@ -283,35 +278,39 @@ pub(crate) async fn download_msgs(context: &Context, session: &mut Session) -> R
|
||||
.await?;
|
||||
|
||||
for rfc724_mid in &rfc724_mids {
|
||||
let res = download_msg(context, rfc724_mid.clone(), session).await;
|
||||
if let Ok(Some(())) = res {
|
||||
delete_from_downloads(context, rfc724_mid).await?;
|
||||
delete_from_available_post_msgs(context, rfc724_mid).await?;
|
||||
}
|
||||
if let Err(err) = res {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to download message rfc724_mid={rfc724_mid}: {:#}.", err
|
||||
);
|
||||
if !msg_is_downloaded_for(context, rfc724_mid).await? {
|
||||
// This is probably a classical email that vanished before we could download it
|
||||
warn!(
|
||||
context,
|
||||
"{rfc724_mid} download failed and there is no downloaded pre-message."
|
||||
);
|
||||
delete_from_downloads(context, rfc724_mid).await?;
|
||||
} else if available_post_msgs_contains_rfc724_mid(context, rfc724_mid).await? {
|
||||
warn!(
|
||||
context,
|
||||
"{rfc724_mid} is in available_post_msgs table but we failed to fetch it,
|
||||
so set the message to DownloadState::Failure - probably it was deleted on the server in the meantime"
|
||||
);
|
||||
set_state_to_failure(context, rfc724_mid).await?;
|
||||
match download_msg(context, rfc724_mid.clone(), session).await {
|
||||
Ok(Some(())) => {
|
||||
delete_from_downloads(context, rfc724_mid).await?;
|
||||
delete_from_available_post_msgs(context, rfc724_mid).await?;
|
||||
} else {
|
||||
// leave the message in DownloadState::InProgress;
|
||||
// it will be downloaded once it arrives.
|
||||
}
|
||||
Ok(None) => {
|
||||
// The message is not available on this transport.
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to download message rfc724_mid={rfc724_mid}: {:#}.", err
|
||||
);
|
||||
if !msg_is_downloaded_for(context, rfc724_mid).await? {
|
||||
// This is probably a classical email that vanished before we could download it
|
||||
warn!(
|
||||
context,
|
||||
"{rfc724_mid} download failed and there is no downloaded pre-message."
|
||||
);
|
||||
delete_from_downloads(context, rfc724_mid).await?;
|
||||
} else if available_post_msgs_contains_rfc724_mid(context, rfc724_mid).await? {
|
||||
warn!(
|
||||
context,
|
||||
"{rfc724_mid} is in available_post_msgs table but we failed to fetch it,
|
||||
so set the message to DownloadState::Failure - probably it was deleted on the server in the meantime"
|
||||
);
|
||||
set_state_to_failure(context, rfc724_mid).await?;
|
||||
delete_from_downloads(context, rfc724_mid).await?;
|
||||
delete_from_available_post_msgs(context, rfc724_mid).await?;
|
||||
} else {
|
||||
// leave the message in DownloadState::InProgress;
|
||||
// it will be downloaded once it arrives.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
src/e2ee.rs
37
src/e2ee.rs
@@ -83,29 +83,20 @@ impl EncryptHelper {
|
||||
None
|
||||
};
|
||||
|
||||
let shared_secret = shared_secret.to_string();
|
||||
let mut raw_message = Vec::new();
|
||||
let cursor = Cursor::new(&mut raw_message);
|
||||
mail_to_encrypt.clone().write_part(cursor).ok();
|
||||
|
||||
let ctext =
|
||||
pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress).await?;
|
||||
let ctext = tokio::task::spawn_blocking(move || {
|
||||
pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(ctext)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a private key exists for the configured user.
|
||||
///
|
||||
/// Normally the private key is generated when the first message is
|
||||
/// sent but in a few locations there are no such guarantees,
|
||||
/// e.g. when exporting keys, and calling this function ensures a
|
||||
/// private key will be present.
|
||||
// TODO, remove this once deltachat::key::Key no longer exists.
|
||||
pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
||||
load_self_public_key(context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -115,23 +106,7 @@ mod tests {
|
||||
use crate::message::Message;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{TestContext, TestContextManager};
|
||||
|
||||
mod ensure_secret_key_exists {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_prexisting() {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_not_configured() {
|
||||
let t = TestContext::new().await;
|
||||
assert!(ensure_secret_key_exists(&t).await.is_err());
|
||||
}
|
||||
}
|
||||
use crate::test_utils::TestContextManager;
|
||||
|
||||
#[test]
|
||||
fn test_mailmime_parse() {
|
||||
|
||||
24
src/html.rs
24
src/html.rs
@@ -21,6 +21,7 @@ use crate::message::{Message, MsgId};
|
||||
use crate::mimeparser::parse_message_id;
|
||||
use crate::param::{Param::SendHtml, Params};
|
||||
use crate::plaintext::PlainText;
|
||||
use crate::simplify::unescape_message_footer_marks;
|
||||
use crate::sql;
|
||||
use crate::tools::{buf_compress, buf_decompress};
|
||||
|
||||
@@ -174,14 +175,14 @@ impl HtmlMsgParser {
|
||||
if self.html.is_empty()
|
||||
&& let Ok(decoded_data) = mail.get_body()
|
||||
{
|
||||
self.html = decoded_data;
|
||||
self.html = unescape_message_footer_marks(&decoded_data);
|
||||
}
|
||||
} else if mimetype == mime::TEXT_PLAIN
|
||||
&& self.plain.is_none()
|
||||
&& let Ok(decoded_data) = mail.get_body()
|
||||
{
|
||||
self.plain = Some(PlainText {
|
||||
text: decoded_data,
|
||||
text: unescape_message_footer_marks(&decoded_data),
|
||||
flowed: if let Some(format) = mail.ctype.params.get("format") {
|
||||
format.as_str().eq_ignore_ascii_case("flowed")
|
||||
} else {
|
||||
@@ -417,6 +418,25 @@ test some special html-characters as < > and & but also " and &#x
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_htmlparse_plain_escaped_footer() {
|
||||
let t = TestContext::new().await;
|
||||
let raw = include_bytes!("../test-data/message/text_plain_escaped_footer.eml");
|
||||
let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).unwrap();
|
||||
assert_eq!(
|
||||
parser.html,
|
||||
r#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head><body dir="auto" style="unicode-bidi: plaintext">
|
||||
-- escaped footer 1<br/>
|
||||
-- escaped footer 2<br/>
|
||||
</body></html>
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_htmlparse_html() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
153
src/imap.rs
153
src/imap.rs
@@ -9,6 +9,7 @@ use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
iter::Peekable,
|
||||
mem::take,
|
||||
str::FromStr,
|
||||
sync::atomic::Ordering,
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
@@ -34,7 +35,6 @@ use crate::message::{self, Message};
|
||||
use crate::mimeparser;
|
||||
use crate::net::proxy::ProxyConfig;
|
||||
use crate::net::session::SessionStream;
|
||||
use crate::oauth2::get_oauth2_access_token;
|
||||
use crate::push::encrypt_device_token;
|
||||
use crate::receive_imf::{
|
||||
ReceivedMsg, from_field_to_contact_id, get_prefetch_parent_message, receive_imf_inner,
|
||||
@@ -76,9 +76,6 @@ pub(crate) struct Imap {
|
||||
|
||||
pub(crate) idle_interrupt_receiver: Receiver<()>,
|
||||
|
||||
/// Email address.
|
||||
pub(crate) addr: String,
|
||||
|
||||
/// Login parameters.
|
||||
lp: Vec<ConfiguredServerLoginParam>,
|
||||
|
||||
@@ -90,8 +87,6 @@ pub(crate) struct Imap {
|
||||
|
||||
strict_tls: bool,
|
||||
|
||||
oauth2: bool,
|
||||
|
||||
/// Watched folder.
|
||||
pub(crate) folder: String,
|
||||
|
||||
@@ -118,12 +113,6 @@ pub(crate) struct Imap {
|
||||
pub(crate) resync_request_receiver: async_channel::Receiver<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OAuth2 {
|
||||
user: String,
|
||||
access_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ServerMetadata {
|
||||
/// IMAP METADATA `/shared/comment` as defined in
|
||||
@@ -136,6 +125,9 @@ pub(crate) struct ServerMetadata {
|
||||
|
||||
pub iroh_relay: Option<Url>,
|
||||
|
||||
/// Maximum number of recipients for SMTP `RCPT TO:`.
|
||||
pub max_smtp_rcpt_to: Option<u32>,
|
||||
|
||||
/// ICE servers for WebRTC calls.
|
||||
pub ice_servers: Vec<UnresolvedIceServer>,
|
||||
|
||||
@@ -148,17 +140,6 @@ pub(crate) struct ServerMetadata {
|
||||
pub ice_servers_expiration_timestamp: i64,
|
||||
}
|
||||
|
||||
impl async_imap::Authenticator for OAuth2 {
|
||||
type Response = String;
|
||||
|
||||
fn process(&mut self, _data: &[u8]) -> Self::Response {
|
||||
format!(
|
||||
"user={}\x01auth=Bearer {}\x01\x01",
|
||||
self.user, self.access_token
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum FolderMeaning {
|
||||
Unknown,
|
||||
@@ -250,9 +231,7 @@ impl Imap {
|
||||
let lp = param.imap.clone();
|
||||
let password = param.imap_password.clone();
|
||||
let proxy_config = ProxyConfig::load(context).await?;
|
||||
let addr = ¶m.addr;
|
||||
let strict_tls = param.strict_tls(proxy_config.is_some());
|
||||
let oauth2 = param.oauth2;
|
||||
let folder = param
|
||||
.imap_folder
|
||||
.clone()
|
||||
@@ -262,12 +241,10 @@ impl Imap {
|
||||
Ok(Imap {
|
||||
transport_id,
|
||||
idle_interrupt_receiver,
|
||||
addr: addr.to_string(),
|
||||
lp,
|
||||
password,
|
||||
proxy_config,
|
||||
strict_tls,
|
||||
oauth2,
|
||||
folder,
|
||||
authentication_failed_once: false,
|
||||
connectivity: Default::default(),
|
||||
@@ -350,8 +327,8 @@ impl Imap {
|
||||
|
||||
let login_params = prioritize_server_login_params(&context.sql, &self.lp, "imap").await?;
|
||||
let mut first_error = None;
|
||||
for lp in login_params {
|
||||
info!(context, "IMAP trying to connect to {}.", &lp.connection);
|
||||
'candidate: for lp in login_params {
|
||||
info!(context, "IMAP trying to connect to {}.", lp.connection);
|
||||
let connection_candidate = lp.connection.clone();
|
||||
let client = match Client::connect(
|
||||
context,
|
||||
@@ -366,7 +343,7 @@ impl Imap {
|
||||
Err(err) => {
|
||||
warn!(context, "{err:#}.");
|
||||
first_error.get_or_insert(err);
|
||||
continue;
|
||||
continue 'candidate;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -376,37 +353,41 @@ impl Imap {
|
||||
let imap_user: &str = lp.user.as_ref();
|
||||
let imap_pw: &str = &self.password;
|
||||
|
||||
let login_res = if self.oauth2 {
|
||||
info!(context, "Logging into IMAP server with OAuth 2.");
|
||||
let addr: &str = self.addr.as_ref();
|
||||
|
||||
let token = get_oauth2_access_token(context, addr, imap_pw, true)
|
||||
.await?
|
||||
.context("IMAP could not get OAUTH token")?;
|
||||
let auth = OAuth2 {
|
||||
user: imap_user.into(),
|
||||
access_token: token,
|
||||
};
|
||||
client.authenticate("XOAUTH2", auth).await
|
||||
} else {
|
||||
info!(context, "Logging into IMAP server with LOGIN.");
|
||||
client.login(imap_user, imap_pw).await
|
||||
};
|
||||
info!(context, "Logging into IMAP server with LOGIN.");
|
||||
let login_res = client.login(imap_user, imap_pw).await;
|
||||
|
||||
match login_res {
|
||||
Ok(mut session) => {
|
||||
let capabilities = determine_capabilities(&mut session).await?;
|
||||
Ok((mut session, login_capabilities_opt)) => {
|
||||
let capabilities = if let Some(login_capabilities) = login_capabilities_opt {
|
||||
login_capabilities
|
||||
} else {
|
||||
// OK response did not contain the CAPABILITY response code.
|
||||
// Request capabilities explicitly.
|
||||
match determine_capabilities(&mut session).await {
|
||||
Ok(capabilities) => capabilities,
|
||||
Err(err) => {
|
||||
warn!(context, "Failed to determine capabilities: {err:#}.");
|
||||
continue 'candidate;
|
||||
}
|
||||
}
|
||||
};
|
||||
let resync_request_sender = self.resync_request_sender.clone();
|
||||
|
||||
let session = if capabilities.can_compress {
|
||||
info!(context, "Enabling IMAP compression.");
|
||||
let compressed_session = session
|
||||
let compressed_session = match session
|
||||
.compress(|s| {
|
||||
let session_stream: Box<dyn SessionStream> = Box::new(s);
|
||||
session_stream
|
||||
})
|
||||
.await
|
||||
.context("Failed to enable IMAP compression")?;
|
||||
{
|
||||
Ok(compressed_session) => compressed_session,
|
||||
Err(err) => {
|
||||
warn!(context, "Failed to enable IMAP compression: {err:#}.");
|
||||
continue 'candidate;
|
||||
}
|
||||
};
|
||||
Session::new(
|
||||
compressed_session,
|
||||
capabilities,
|
||||
@@ -1117,7 +1098,7 @@ impl Session {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"store_seen_flags_on_imap: Failed to select {folder}, will retry later: {err:#}."
|
||||
"store_seen_flags_on_imap: Transport {transport_id}: Failed to select {folder}, will retry later: {err:#}."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -1128,13 +1109,13 @@ impl Session {
|
||||
} else if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
|
||||
"Transport {transport_id}: Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Marked messages {} in folder {} as seen.", uid_set, folder
|
||||
"Transport {transport_id}: Marked messages {uid_set} in folder {folder} as seen."
|
||||
);
|
||||
}
|
||||
context
|
||||
@@ -1182,6 +1163,7 @@ impl Session {
|
||||
|
||||
for (request_uids, set) in build_sequence_sets(&request_uids)? {
|
||||
info!(context, "Starting UID FETCH of message set \"{}\".", set);
|
||||
let transport_id = self.transport_id();
|
||||
let mut fetch_responses = self
|
||||
.uid_fetch(&set, BODY_FULL)
|
||||
.await
|
||||
@@ -1277,6 +1259,12 @@ impl Session {
|
||||
"Passing message UID {} to receive_imf().", request_uid
|
||||
);
|
||||
let res = receive_imf_inner(context, rfc724_mid, body, is_seen).await;
|
||||
crate::sql::update_transport_last_rcvd_timestamp(context, transport_id)
|
||||
.await
|
||||
.context(format!(
|
||||
"Failed to update last_rcvd_timestamp of transport {}",
|
||||
transport_id
|
||||
))?;
|
||||
|
||||
// If there was an error receiving the message, show a device message:
|
||||
let received_msg = match res {
|
||||
@@ -1284,7 +1272,9 @@ impl Session {
|
||||
warn!(context, "receive_imf error: {err:#}.");
|
||||
|
||||
let text = format!(
|
||||
"❌ Failed to receive a message: {err:#}. Core version v{DC_VERSION_STR}. Please report this bug to delta@merlinux.eu or https://support.delta.chat/.",
|
||||
// No trailing '.' to avoid from the Android UI treating it as a part of
|
||||
// URL.
|
||||
"❌ Failed to receive a message: {err:#}. Core version v{DC_VERSION_STR}. Please report this bug to delta@merlinux.eu or https://support.delta.chat/",
|
||||
);
|
||||
let mut msg = Message::new_text(text);
|
||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||
@@ -1338,11 +1328,12 @@ impl Session {
|
||||
#[expect(clippy::arithmetic_side_effects)]
|
||||
pub(crate) async fn update_metadata(&mut self, context: &Context) -> Result<()> {
|
||||
let mut lock = context.metadata.write().await;
|
||||
let transport_id = self.transport_id();
|
||||
|
||||
if !self.can_metadata() {
|
||||
*lock = Some(Default::default());
|
||||
lock.entry(transport_id).or_default();
|
||||
}
|
||||
if let Some(ref mut old_metadata) = *lock {
|
||||
if let Some(old_metadata) = lock.get_mut(&transport_id) {
|
||||
let now = time();
|
||||
|
||||
// Refresh TURN server credentials if they expire in 12 hours.
|
||||
@@ -1392,6 +1383,7 @@ impl Session {
|
||||
let mut comment = None;
|
||||
let mut admin = None;
|
||||
let mut iroh_relay = None;
|
||||
let mut max_smtp_rcpt_to = None;
|
||||
let mut ice_servers = None;
|
||||
let mut ice_servers_expiration_timestamp = 0;
|
||||
|
||||
@@ -1401,7 +1393,7 @@ impl Session {
|
||||
.get_metadata(
|
||||
mailbox,
|
||||
options,
|
||||
"(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn)",
|
||||
"(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn /shared/vendor/deltachat/maxsmtprecipients)",
|
||||
)
|
||||
.await?;
|
||||
for m in metadata {
|
||||
@@ -1437,6 +1429,18 @@ impl Session {
|
||||
}
|
||||
}
|
||||
}
|
||||
"/shared/vendor/deltachat/maxsmtprecipients" => {
|
||||
if let Some(value) = m.value {
|
||||
if let Ok(limit) = u32::from_str(&value) {
|
||||
max_smtp_rcpt_to = Some(limit);
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"Got invalid maxsmtprecipients metadata: {:?}.", value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1448,13 +1452,17 @@ impl Session {
|
||||
create_fallback_ice_servers()
|
||||
};
|
||||
|
||||
*lock = Some(ServerMetadata {
|
||||
comment,
|
||||
admin,
|
||||
iroh_relay,
|
||||
ice_servers,
|
||||
ice_servers_expiration_timestamp,
|
||||
});
|
||||
lock.insert(
|
||||
transport_id,
|
||||
ServerMetadata {
|
||||
comment,
|
||||
admin,
|
||||
iroh_relay,
|
||||
max_smtp_rcpt_to,
|
||||
ice_servers,
|
||||
ice_servers_expiration_timestamp,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1997,23 +2005,6 @@ async fn get_uidvalidity(context: &Context, transport_id: u32, folder: &str) ->
|
||||
.unwrap_or(0))
|
||||
}
|
||||
|
||||
pub(crate) async fn set_modseq(
|
||||
context: &Context,
|
||||
transport_id: u32,
|
||||
folder: &str,
|
||||
modseq: u64,
|
||||
) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO imap_sync (transport_id, folder, modseq) VALUES (?,?,?)
|
||||
ON CONFLICT(transport_id, folder) DO UPDATE SET modseq=excluded.modseq",
|
||||
(transport_id, folder, modseq),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a list of sequence/uid sets. The returned sets have each no more than around 1000
|
||||
/// characters because according to <https://tools.ietf.org/html/rfc2683#section-3.2.1.5>
|
||||
/// command lines should not be much more than 1000 chars (servers should allow at least 8000 chars)
|
||||
|
||||
@@ -53,10 +53,24 @@ fn alpn(port: u16) -> &'static str {
|
||||
pub(crate) async fn determine_capabilities(
|
||||
session: &mut ImapSession<Box<dyn SessionStream>>,
|
||||
) -> Result<Capabilities> {
|
||||
let caps = session
|
||||
let imap_capabilities = session
|
||||
.capabilities()
|
||||
.await
|
||||
.context("CAPABILITY command error")?;
|
||||
identify_server(session, imap_capabilities).await
|
||||
}
|
||||
|
||||
/// Identifies the server by sending ID command if it is supported.
|
||||
///
|
||||
/// Some IMAP servers require sending this command,
|
||||
/// see <https://github.com/chatmail/core/issues/3458>,
|
||||
/// <https://github.com/OfflineIMAP/offlineimap3/issues/71>
|
||||
/// and <https://github.com/pimalaya/himalaya/issues/652>
|
||||
/// for details.
|
||||
pub(crate) async fn identify_server(
|
||||
session: &mut ImapSession<Box<dyn SessionStream>>,
|
||||
caps: async_imap::types::Capabilities,
|
||||
) -> Result<Capabilities> {
|
||||
let server_id = if caps.has_str("ID") {
|
||||
session.id([("name", Some("Delta Chat"))]).await?
|
||||
} else {
|
||||
@@ -82,31 +96,30 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs in with the LOGIN command.
|
||||
///
|
||||
/// If the server supports [ID extension], sends ID command
|
||||
/// and records the server response in the [`Capabilities`] structure.
|
||||
///
|
||||
/// [ID extension]: https://datatracker.ietf.org/doc/rfc2971/
|
||||
pub(crate) async fn login(
|
||||
self,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<ImapSession<Box<dyn SessionStream>>> {
|
||||
) -> Result<(ImapSession<Box<dyn SessionStream>>, Option<Capabilities>)> {
|
||||
let Client { inner, .. } = self;
|
||||
|
||||
let session = inner
|
||||
.login(username, password)
|
||||
let (mut session, login_capabilities_opt) = inner
|
||||
.login_with_capabilities(username, password)
|
||||
.await
|
||||
.map_err(|(err, _client)| err)?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
pub(crate) async fn authenticate(
|
||||
self,
|
||||
auth_type: &str,
|
||||
authenticator: impl async_imap::Authenticator,
|
||||
) -> Result<ImapSession<Box<dyn SessionStream>>> {
|
||||
let Client { inner, .. } = self;
|
||||
let session = inner
|
||||
.authenticate(auth_type, authenticator)
|
||||
.await
|
||||
.map_err(|(err, _client)| err)?;
|
||||
Ok(session)
|
||||
let capabilities = if let Some(login_capabilities) = login_capabilities_opt {
|
||||
Some(identify_server(&mut session, login_capabilities).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((session, capabilities))
|
||||
}
|
||||
|
||||
async fn connection_attempt(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use anyhow::Context as _;
|
||||
|
||||
use super::session::Session as ImapSession;
|
||||
use super::{get_uid_next, get_uidvalidity, set_modseq, set_uid_next, set_uidvalidity};
|
||||
use super::{get_uid_next, get_uidvalidity, set_uid_next, set_uidvalidity};
|
||||
use crate::context::Context;
|
||||
use crate::ensure_and_debug_assert;
|
||||
use crate::log::warn;
|
||||
@@ -200,9 +200,6 @@ impl ImapSession {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// UIDVALIDITY is modified, reset highest seen MODSEQ.
|
||||
set_modseq(context, transport_id, folder, 0).await?;
|
||||
|
||||
// ============== uid_validity has changed or is being set the first time. ==============
|
||||
|
||||
let new_uid_next = new_uid_next.unwrap_or_default();
|
||||
|
||||
@@ -111,7 +111,7 @@ impl Session {
|
||||
}
|
||||
|
||||
// Returns true if IMAP server has `XCHATMAIL` capability.
|
||||
pub fn is_chatmail(&self) -> bool {
|
||||
pub(crate) fn is_chatmail(&self) -> bool {
|
||||
self.capabilities.is_chatmail
|
||||
}
|
||||
|
||||
|
||||
19
src/imex.rs
19
src/imex.rs
@@ -17,7 +17,6 @@ use crate::blob::BlobDirContents;
|
||||
use crate::chat::delete_and_reset_all_device_msgs;
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::e2ee;
|
||||
use crate::events::EventType;
|
||||
use crate::key::{self, DcKey, SignedSecretKey};
|
||||
use crate::log::{LogExt, warn};
|
||||
@@ -170,7 +169,7 @@ async fn imex_inner(
|
||||
|
||||
if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys {
|
||||
// before we export anything, make sure the private key exists
|
||||
e2ee::ensure_secret_key_exists(context)
|
||||
key::ensure_secret_key_exists(context)
|
||||
.await
|
||||
.context("Cannot create private key or private key not available")?;
|
||||
|
||||
@@ -454,7 +453,7 @@ async fn export_backup(context: &Context, dir: &Path, passphrase: String) -> Res
|
||||
let temp_db_path = TempPathGuard::new(temp_db_path);
|
||||
let temp_path = TempPathGuard::new(temp_path);
|
||||
|
||||
export_database(context, &temp_db_path, passphrase, now)
|
||||
export_database(context, &temp_db_path, passphrase)
|
||||
.await
|
||||
.context("could not export database")?;
|
||||
|
||||
@@ -674,7 +673,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
|
||||
.await?;
|
||||
let self_addr = context.get_primary_self_addr().await?;
|
||||
for (id, private_key, is_default) in keys {
|
||||
let id = Some(id).filter(|_| is_default == 0);
|
||||
let id = (is_default == 0).then_some(id);
|
||||
|
||||
let Ok(private_key) = private_key else {
|
||||
export_errors += 1;
|
||||
@@ -738,17 +737,11 @@ where
|
||||
/// overwritten.
|
||||
///
|
||||
/// This also verifies that IO is not running during the export.
|
||||
async fn export_database(
|
||||
context: &Context,
|
||||
dest: &Path,
|
||||
passphrase: String,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
async fn export_database(context: &Context, dest: &Path, passphrase: String) -> Result<()> {
|
||||
ensure!(
|
||||
!context.scheduler.is_running().await,
|
||||
"cannot export backup, IO is running"
|
||||
);
|
||||
let timestamp = timestamp.try_into().context("32-bit UNIX time overflow")?;
|
||||
|
||||
// TODO: Maybe introduce camino crate for UTF-8 paths where we need them.
|
||||
let dest = dest
|
||||
@@ -756,10 +749,6 @@ async fn export_database(
|
||||
.with_context(|| format!("path {} is not valid unicode", dest.display()))?;
|
||||
|
||||
context.set_config(Config::BccSelf, Some("1")).await?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int("backup_time", timestamp)
|
||||
.await?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int("backup_version", DCBACKUP_VERSION)
|
||||
|
||||
@@ -30,6 +30,7 @@ use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result, bail, format_err};
|
||||
use futures_lite::FutureExt;
|
||||
@@ -38,15 +39,16 @@ use tokio::fs;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::EventType;
|
||||
use crate::chat::add_device_msg;
|
||||
use crate::context::Context;
|
||||
use crate::imex::BlobDirContents;
|
||||
use crate::key;
|
||||
use crate::log::warn;
|
||||
use crate::message::Message;
|
||||
use crate::qr::Qr;
|
||||
use crate::stock_str::backup_transfer_msg_body;
|
||||
use crate::tools::{TempPathGuard, create_id, time};
|
||||
use crate::{EventType, e2ee};
|
||||
use crate::tools::{TempPathGuard, create_id};
|
||||
|
||||
use super::{DBFILE_BACKUP_NAME, export_backup_stream, export_database, import_backup_stream};
|
||||
|
||||
@@ -112,7 +114,7 @@ impl BackupProvider {
|
||||
.context("Context dir not found")?;
|
||||
|
||||
// before we export, make sure the private key exists
|
||||
e2ee::ensure_secret_key_exists(context)
|
||||
key::ensure_secret_key_exists(context)
|
||||
.await
|
||||
.context("Cannot create private key or private key not available")?;
|
||||
|
||||
@@ -128,7 +130,7 @@ impl BackupProvider {
|
||||
|
||||
let passphrase = String::new();
|
||||
|
||||
export_database(context, &dbfile, passphrase, time())
|
||||
export_database(context, &dbfile, passphrase)
|
||||
.await
|
||||
.context("Database export failed")?;
|
||||
|
||||
@@ -304,9 +306,12 @@ pub async fn get_backup2(
|
||||
) -> Result<()> {
|
||||
let relay_mode = RelayMode::Disabled;
|
||||
|
||||
let mut transport_config = iroh::endpoint::TransportConfig::default();
|
||||
transport_config.max_idle_timeout(Some(Duration::from_secs(60).try_into()?));
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
.relay_mode(relay_mode)
|
||||
.transport_config(transport_config)
|
||||
.bind()
|
||||
.await?;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user