Compare commits

..

6 Commits

Author SHA1 Message Date
link2xt
cde0fb32e6 fix(node): undefine NAPI_EXPERIMENTAL
This fixes build with Node v20.12.2.

Related upstream issue: <https://github.com/nodejs/node/issues/52229>
2024-04-22 22:44:03 +00:00
link2xt
f1bbd676c6 x 2024-04-22 22:36:52 +00:00
link2xt
d924637830 x 2024-04-22 22:33:31 +00:00
link2xt
6aa044a70a x 2024-04-22 22:28:01 +00:00
link2xt
8a827495ef ci(node): use newer xcode 2024-04-22 22:25:13 +00:00
link2xt
e290a7b852 ci(node): test against node 20 2024-04-22 21:57:18 +00:00
51 changed files with 211 additions and 574 deletions

View File

@@ -21,14 +21,17 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-14, windows-latest]
node: ["18", "20"]
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- uses: actions/setup-node@v4
with:
node-version: "18"
node-version: ${{ matrix.node }}
- if: matrix.os == 'macos-14'
run: sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer
- name: System info
run: |
rustc -vV

View File

@@ -1,48 +1,5 @@
# Changelog
## [1.137.4] - 2024-04-24
### API-Changes
- [**breaking**] Remove `Stream` implementation for `EventEmitter`.
- Experimental Webxdc Integration API, Maps Integration ([#5461](https://github.com/deltachat/deltachat-core-rust/pull/5461)).
### Features / Changes
- Add progressive backoff for failing IMAP connection attempts ([#5443](https://github.com/deltachat/deltachat-core-rust/pull/5443)).
- Replace event channel with broadcast channel.
- Mark contact request messages as seen on IMAP.
### Fixes
- Convert images to RGB8 (without alpha) before encoding into JPEG to fix sending of large RGBA images.
- Don't set `is_bot` for webxdc status updates ([#5445](https://github.com/deltachat/deltachat-core-rust/pull/5445)).
- Do not fail if Autocrypt Setup Message has no encryption preference to fix key transfer from K-9 Mail to Delta Chat.
- Use only CRLF in Autocrypt Setup Message.
- python: Use cached message object if `dc_get_msg()` returns `NULL`.
- python: `Message::is_outgoing`: Don't reload message from db.
- python: `_map_ffi_event`: Always check if `get_message_by_id()` returned None.
- node: Undefine `NAPI_EXPERIMENTAL` to fix build with new clang.
### Build system
- nix: Add `imap-tools` as `deltachat-rpc-client` dependency.
- nix: Add `./deltachat-contact-tools` to sources.
- nix: Update nix flake.
- deps: Update rustls to 0.21.11.
### Documentation
- Update references to SecureJoin protocols.
- Fix broken references in documentation comments.
### Refactor
- imap: remove `RwLock` from `ratelimit`.
- deltachat-ffi: Remove unused `ResultNullableExt`.
- Remove duplicate clippy exceptions.
- Group `use` at the top of the test modules.
## [1.137.3] - 2024-04-16
### API-Changes
@@ -3982,4 +3939,3 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.137.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.0...v1.137.1
[1.137.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.1...v1.137.2
[1.137.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.2...v1.137.3
[1.137.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.3...v1.137.4

10
Cargo.lock generated
View File

@@ -1157,7 +1157,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.137.4"
version = "1.137.3"
dependencies = [
"ansi_term",
"anyhow",
@@ -1249,7 +1249,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.137.4"
version = "1.137.3"
dependencies = [
"anyhow",
"async-channel 2.2.0",
@@ -1273,7 +1273,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.137.4"
version = "1.137.3"
dependencies = [
"ansi_term",
"anyhow",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.137.4"
version = "1.137.3"
dependencies = [
"anyhow",
"deltachat",
@@ -1317,7 +1317,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.137.4"
version = "1.137.3"
dependencies = [
"anyhow",
"deltachat",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.137.4"
version = "1.137.3"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.77"

View File

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

View File

@@ -4105,19 +4105,6 @@ char* dc_msg_get_subject (const dc_msg_t* msg);
char* dc_msg_get_file (const dc_msg_t* msg);
/**
* Save file copy at the user-provided path.
*
* Fails if file already exists at the provided path.
*
* @memberof dc_msg_t
* @param msg The message object.
* @param path Destination file path with filename and extension.
* @return 0 on failure, 1 on success.
*/
int dc_msg_save_file (const dc_msg_t* msg, const char* path);
/**
* Get an original attachment filename, with extension but without the path. To get the full path,
* use dc_msg_get_file().

View File

@@ -3368,34 +3368,6 @@ pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_cha
.unwrap_or_else(|| "".strdup())
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_save_file(
msg: *mut dc_msg_t,
path: *const libc::c_char,
) -> libc::c_int {
if msg.is_null() || path.is_null() {
eprintln!("ignoring careless call to dc_msg_save_file()");
return 0;
}
let ffi_msg = &*msg;
let ctx = &*ffi_msg.context;
let path = to_string_lossy(path);
let r = block_on(
ffi_msg
.message
.save_file(ctx, &std::path::PathBuf::from(path)),
);
match r {
Ok(()) => 1,
Err(_) => {
r.context("Failed to save file from message")
.log_err(ctx)
.unwrap_or_default();
0
}
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c_char {
if msg.is_null() {

View File

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

View File

@@ -1,5 +1,4 @@
use std::collections::BTreeMap;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use std::{collections::HashMap, str::FromStr};
@@ -1879,15 +1878,6 @@ impl CommandApi {
Ok(can_send)
}
/// Saves a file copy at the user-provided path.
///
/// Fails if file already exists at the provided path.
async fn save_msg_file(&self, account_id: u32, msg_id: u32, path: String) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let message = Message::load_from_db(&ctx, MsgId::new(msg_id)).await?;
message.save_file(&ctx, Path::new(&path)).await
}
// ---------------------------------------------
// functions for the composer
// the composer is the message input field
@@ -1960,21 +1950,19 @@ impl CommandApi {
);
let destination_path = account_folder.join("stickers").join(collection);
fs::create_dir_all(&destination_path).await?;
let file = message.get_filename().context("no file?")?;
message
.save_file(
&ctx,
&destination_path.join(format!(
"{}.{}",
msg_id,
Path::new(&file)
.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
)),
)
.await?;
let file = message.get_file(&ctx).context("no file")?;
fs::copy(
&file,
destination_path.join(format!(
"{}.{}",
msg_id,
file.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
)),
)
.await?;
Ok(())
}

View File

@@ -53,5 +53,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.137.4"
"version": "1.137.3"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.137.4"
version = "1.137.3"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/deltachat/deltachat-core-rust"

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "1.137.4"
version = "1.137.3"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -37,7 +37,7 @@ deltachat_rpc_client = [
line-length = 120
[tool.ruff]
lint.select = [
select = [
"E", "W", # pycodestyle
"F", # Pyflakes
"N", # pep8-naming

View File

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

View File

@@ -1,13 +1,9 @@
import { join } from 'path'
import * as url from 'url'
/**
* bindings are not typed yet.
* if the available function names are required they can be found inside of `../src/module.c`
*/
import build from 'node-gyp-build'
export const bindings: any = build(
join(url.fileURLToPath(new URL('.', import.meta.url)), '../')
)
export const bindings: any = require('node-gyp-build')(join(__dirname, '../'))
export default bindings

View File

@@ -1,11 +1,11 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import binding from './binding'
import rawDebug from 'debug'
const debug = rawDebug('deltachat:node:chat')
import { C } from './constants.js'
import { integerToHexColor } from './util.js'
import { ChatJSON } from './types.js'
import { C } from './constants'
import { integerToHexColor } from './util'
import { ChatJSON } from './types'
interface NativeChat {}
/**

View File

@@ -1,10 +1,9 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import { Lot } from './lot.js'
import { Chat } from './chat.js'
import rawDebug from 'debug'
const debug = rawDebug('deltachat:node:chatlist')
import binding from './binding'
import { Lot } from './lot'
import { Chat } from './chat'
const debug = require('debug')('deltachat:node:chatlist')
interface NativeChatList {}
/**

View File

@@ -1,10 +1,9 @@
import { integerToHexColor } from './util.js'
import { integerToHexColor } from './util'
/* eslint-disable camelcase */
import binding from './binding.js'
import rawDebug from 'debug'
const debug = rawDebug('deltachat:node:contact')
import binding from './binding'
const debug = require('debug')('deltachat:node:contact')
interface NativeContact {}
/**

View File

@@ -1,17 +1,17 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import { C, EventId2EventName } from './constants.js'
import { Chat } from './chat.js'
import { ChatList } from './chatlist.js'
import { Contact } from './contact.js'
import { Message } from './message.js'
import { Lot } from './lot.js'
import { Locations } from './locations.js'
import binding from './binding'
import { C, EventId2EventName } from './constants'
import { Chat } from './chat'
import { ChatList } from './chatlist'
import { Contact } from './contact'
import { Message } from './message'
import { Lot } from './lot'
import { Locations } from './locations'
import rawDebug from 'debug'
import { AccountManager } from './deltachat.js'
import { AccountManager } from './deltachat'
import { join } from 'path'
import { EventEmitter } from 'events'
import { EventEmitter } from 'stream'
const debug = rawDebug('deltachat:node:index')
const noop = function () {}

View File

@@ -1,13 +1,13 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import { EventId2EventName } from './constants.js'
import binding from './binding'
import { EventId2EventName } from './constants'
import { EventEmitter } from 'events'
import { existsSync } from 'fs'
import rawDebug from 'debug'
import { tmpdir } from 'os'
import { join } from 'path'
import { Context } from './context.js'
import { Context } from './context'
const debug = rawDebug('deltachat:node:index')
const noop = function () {}

View File

@@ -1,20 +1,20 @@
import { AccountManager } from './deltachat.js'
import { AccountManager } from './deltachat'
export default AccountManager
export { Context } from './context.js'
export { Chat } from './chat.js'
export { ChatList } from './chatlist.js'
export { C } from './constants.js'
export { Contact } from './contact.js'
export { Context } from './context'
export { Chat } from './chat'
export { ChatList } from './chatlist'
export { C } from './constants'
export { Contact } from './contact'
export { AccountManager as DeltaChat }
export { Locations } from './locations.js'
export { Lot } from './lot.js'
export { Locations } from './locations'
export { Lot } from './lot'
export {
Message,
MessageState,
MessageViewType,
MessageDownloadState,
} from './message.js'
} from './message'
export * from './types.js'
export * from './types'

View File

@@ -1,8 +1,7 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import rawDebug from 'debug'
const debug = rawDebug('deltachat:node:locations')
const binding = require('../binding')
const debug = require('debug')('deltachat:node:locations')
interface NativeLocations {}
/**

View File

@@ -1,8 +1,7 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import rawDebug from 'debug'
const debug = rawDebug('deltachat:node:lot')
const binding = require('../binding')
const debug = require('debug')('deltachat:node:lot')
interface NativeLot {}
/**

View File

@@ -1,12 +1,11 @@
/* eslint-disable camelcase */
import binding from './binding.js'
import { C } from './constants.js'
import { Lot } from './lot.js'
import { Chat } from './chat.js'
import { WebxdcInfo } from './context.js'
import rawDebug from 'debug'
const debug = rawDebug('deltachat:node:message')
import binding from './binding'
import { C } from './constants'
import { Lot } from './lot'
import { Chat } from './chat'
import { WebxdcInfo } from './context'
const debug = require('debug')('deltachat:node:message')
export enum MessageDownloadState {
Available = C.DC_DOWNLOAD_AVAILABLE,

View File

@@ -1 +0,0 @@
declare module 'node-gyp-build'

View File

@@ -1,4 +1,4 @@
import { C } from './constants.js'
import { C } from './constants'
export type ChatTypes =
| C.DC_CHAT_TYPE_GROUP

View File

@@ -1,4 +1,4 @@
import { spawnSync } from 'child_process'
const spawnSync = require('child_process').spawnSync
const verbose = isVerbose()
@@ -23,4 +23,4 @@ function isVerbose () {
return loglevel === 'verbose' || process.env.CI === 'true'
}
export { spawn, log, isVerbose, verbose }
module.exports = { spawn, log, isVerbose, verbose }

View File

@@ -1,16 +1,14 @@
#!/usr/bin/env node
import fs from 'fs'
import path from 'path'
import * as url from 'url'
const fs = require('fs')
const path = require('path')
const data = []
const header = path.resolve(url.fileURLToPath(new URL('.', import.meta.url)), '../../deltachat-ffi/deltachat.h')
const header = path.resolve(__dirname, '../../deltachat-ffi/deltachat.h')
console.log('Generating constants...')
const header_data = fs.readFileSync(header, 'UTF-8')
const regex = /^#define\s+(\w+)\s+(\w+)/gm
var match
while (null != (match = regex.exec(header_data))) {
const key = match[1]
const value = parseInt(match[2])
@@ -19,6 +17,8 @@ while (null != (match = regex.exec(header_data))) {
}
}
delete header_data
const constants = data
.filter(
({ key }) => key.toUpperCase()[0] === key[0] // check if define name is uppercase
@@ -49,17 +49,17 @@ const events = data
// backwards compat
fs.writeFileSync(
path.resolve(url.fileURLToPath(new URL('.', import.meta.url)), '../constants.js'),
path.resolve(__dirname, '../constants.js'),
`// Generated!\n\nmodule.exports = {\n${constants}\n}\n`
)
// backwards compat
fs.writeFileSync(
path.resolve(url.fileURLToPath(new URL('.', import.meta.url)), '../events.js'),
path.resolve(__dirname, '../events.js'),
`/* eslint-disable quotes */\n// Generated!\n\nmodule.exports = {\n${events}\n}\n`
)
fs.writeFileSync(
path.resolve(url.fileURLToPath(new URL('.', import.meta.url)), '../lib/constants.js'),
path.resolve(__dirname, '../lib/constants.ts'),
`// Generated!\n\nexport enum C {\n${constants.replace(/:/g, ' =')},\n}\n
// Generated!\n\nexport const EventId2EventName: { [key: number]: string } = {\n${events},\n}\n`
)

View File

@@ -1,7 +1,6 @@
import {execSync} from 'child_process'
import {existsSync} from 'fs'
import {join} from 'path'
import * as url from 'url'
const {execSync} = require('child_process')
const {existsSync} = require('fs')
const {join} = require('path')
const run = (cmd) => {
console.log('[i] running `' + cmd + '`')
@@ -17,7 +16,7 @@ if (process.env.USE_SYSTEM_LIBDELTACHAT === 'true') {
run('npm run install:prebuilds')
}
if (!existsSync(join(url.fileURLToPath(new URL('.', import.meta.url)), '..', 'dist'))) {
if (!existsSync(join(__dirname, '..', 'dist'))) {
console.log('[i] Didn\'t find already built typescript bindings. Trying to transpile them. If this fail, make sure typescript is installed ;)')
run('npm run build:bindings:ts')
}

View File

@@ -1,5 +1,4 @@
import { readFileSync } from 'fs'
import { request } from 'https'
const { readFileSync } = require('fs')
const sha = JSON.parse(
readFileSync(process.env['GITHUB_EVENT_PATH'], 'utf8')
@@ -22,6 +21,8 @@ const STATUS_DATA = {
target_url: base_url + file_url,
}
const http = require('https')
const options = {
method: 'POST',
headers: {
@@ -31,7 +32,7 @@ const options = {
},
}
const req = request(GITHUB_API_URL, options, function (res) {
const req = http.request(GITHUB_API_URL, options, function(res) {
var chunks = []
res.on('data', function(chunk) {
chunks.push(chunk)

View File

@@ -1,6 +1,5 @@
import fs from 'fs'
import path from 'path'
import * as url from 'url'
const fs = require('fs')
const path = require('path')
if (process.platform !== 'win32') {
console.log('postinstall: not windows, so skipping!')
@@ -8,7 +7,7 @@ if (process.platform !== 'win32') {
}
const from = path.resolve(
url.fileURLToPath(new URL('.', import.meta.url)),
__dirname,
'..',
'..',
'target',
@@ -20,7 +19,7 @@ const getDestination = () => {
const argv = process.argv
if (argv.length === 3 && argv[2] === '--prebuild') {
return path.resolve(
url.fileURLToPath(new URL('.', import.meta.url)),
__dirname,
'..',
'prebuilds',
'win32-x64',
@@ -28,7 +27,7 @@ const getDestination = () => {
)
} else {
return path.resolve(
url.fileURLToPath(new URL('.', import.meta.url)),
__dirname,
'..',
'build',
'Release',

View File

@@ -1,8 +1,7 @@
import path from 'path'
import { spawn } from './common.js'
import * as url from 'url'
const path = require('path')
const { spawn } = require('./common')
const opts = {
cwd: path.resolve(url.fileURLToPath(new URL('.', import.meta.url)), '../..'),
cwd: path.resolve(__dirname, '../..'),
stdio: 'inherit'
}

View File

@@ -3,8 +3,7 @@
"outDir": "dist",
"rootDir": "./lib",
"sourceMap": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"module": "commonjs",
"target": "es5",
"esModuleInterop": true,
"declaration": true,

View File

@@ -1,5 +1,4 @@
{
"type": "module",
"dependencies": {
"debug": "^4.1.1",
"napi-macros": "^2.0.0",
@@ -11,7 +10,7 @@
"@types/node": "^20.8.10",
"chai": "~4.3.10",
"chai-as-promised": "^7.1.1",
"mocha": "^10.2.0",
"mocha": "^8.2.1",
"node-gyp": "^10.0.0",
"prebuildify": "^5.0.1",
"prebuildify-ci": "^1.0.5",
@@ -28,7 +27,7 @@
],
"homepage": "https://github.com/deltachat/deltachat-core-rust/tree/master/node",
"license": "GPL-3.0-or-later",
"exports": "./node/dist/index.js",
"main": "node/dist/index.js",
"name": "deltachat-node",
"repository": {
"type": "git",
@@ -56,5 +55,5 @@
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.137.4"
"version": "1.137.3"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "1.137.4"
version = "1.137.3"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"
requires-python = ">=3.7"
@@ -46,7 +46,7 @@ deltachat = [
line-length = 120
[tool.ruff]
lint.select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032", "ANN204"]
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032", "ANN204"]
line-length = 120
[tool.isort]

View File

@@ -18,14 +18,14 @@ def test_db_busy_error(acfactory):
# make a number of accounts
accounts = acfactory.get_many_online_accounts(3)
log(f"created {len(accounts)} accounts")
log("created %s accounts" % len(accounts))
# put a bigfile into each account
for acc in accounts:
acc.bigfile = os.path.join(acc.get_blobdir(), "bigfile")
with open(acc.bigfile, "wb") as f:
f.write(b"01234567890" * 1000_000)
log(f"created {len(accounts)} bigfiles")
log("created %s bigfiles" % len(accounts))
contact_addrs = [acc.get_self_contact().addr for acc in accounts]
chat = accounts[0].create_group_chat("stress-group")

View File

@@ -1343,6 +1343,7 @@ def test_quote_encrypted(acfactory, lp):
for quoted_msg in msg1, msg3:
# Save the draft with a quote.
# It should be encrypted if quoted message is encrypted.
msg_draft = Message.new_empty(ac1, "text")
msg_draft.set_text("message reply")
msg_draft.quote = quoted_msg
@@ -1356,14 +1357,10 @@ def test_quote_encrypted(acfactory, lp):
chat.set_draft(None)
assert chat.get_draft() is None
# Quote should be replaced with "..." if quoted message is encrypted.
msg_in = ac2._evtracker.wait_next_incoming_message()
assert msg_in.text == "message reply"
assert not msg_in.is_encrypted()
if quoted_msg.is_encrypted():
assert msg_in.quoted_text == "..."
else:
assert msg_in.quoted_text == quoted_msg.text
assert msg_in.quoted_text == quoted_msg.text
assert msg_in.is_encrypted() == quoted_msg.is_encrypted()
def test_quote_attachment(tmp_path, acfactory, lp):

View File

@@ -1 +1 @@
2024-04-24
2024-04-16

View File

@@ -698,10 +698,7 @@ fn encode_img(
ImageOutputFormat::Png => img.write_to(&mut buf, ImageFormat::Png)?,
ImageOutputFormat::Jpeg { quality } => {
let encoder = JpegEncoder::new_with_quality(&mut buf, quality);
// Convert image into RGB8 to avoid the error
// "The encoder or decoder for Jpeg does not support the color type Rgba8"
// (<https://github.com/image-rs/image/issues/2211>).
img.clone().into_rgb8().write_with_encoder(encoder)?;
img.write_with_encoder(encoder)?;
}
}
Ok(())
@@ -1208,28 +1205,6 @@ mod tests {
.unwrap();
}
/// Tests that RGBA PNG can be recoded into JPEG
/// by dropping alpha channel.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_recode_image_rgba_png_to_jpeg() {
let bytes = include_bytes!("../test-data/image/screenshot-rgba.png");
send_image_check_mediaquality(
Viewtype::Image,
Some("1"),
bytes,
"png",
false, // no Exif
1920,
1080,
0,
constants::WORSE_IMAGE_SIZE,
constants::WORSE_IMAGE_SIZE * 1080 / 1920,
)
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_recode_image_huge_jpg() {
let bytes = include_bytes!("../test-data/image/screenshot.jpg");
@@ -1307,26 +1282,23 @@ mod tests {
let alice_msg = alice.get_last_msg().await;
assert_eq!(alice_msg.get_width() as u32, compressed_width);
assert_eq!(alice_msg.get_height() as u32, compressed_height);
let file_saved = alice
.get_blobdir()
.join("saved-".to_string() + &alice_msg.get_filename().unwrap());
alice_msg.save_file(&alice, &file_saved).await?;
check_image_size(file_saved, compressed_width, compressed_height);
check_image_size(
alice_msg.get_file(&alice).unwrap(),
compressed_width,
compressed_height,
);
let bob_msg = bob.recv_msg(&sent).await;
assert_eq!(bob_msg.get_viewtype(), Viewtype::Image);
assert_eq!(bob_msg.get_width() as u32, compressed_width);
assert_eq!(bob_msg.get_height() as u32, compressed_height);
let file_saved = bob
.get_blobdir()
.join("saved-".to_string() + &bob_msg.get_filename().unwrap());
bob_msg.save_file(&bob, &file_saved).await?;
let file = bob_msg.get_file(&bob).unwrap();
let blob = BlobObject::new_from_path(&bob, &file_saved).await?;
let blob = BlobObject::new_from_path(&bob, &file).await?;
let (_, exif) = blob.metadata()?;
assert!(exif.is_none());
let img = check_image_size(file_saved, compressed_width, compressed_height);
let img = check_image_size(file, compressed_width, compressed_height);
Ok(img)
}

View File

@@ -2708,19 +2708,7 @@ Hi."#;
bob.recv_msg(&sent_msg).await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
let green = ansi_term::Color::Green.normal();
assert!(
contact.was_seen_recently(),
"{}",
green.paint(
"\nNOTE: This test failure is probably a false-positive, caused by tests running in parallel.
The issue is that `SystemTime::shift()` (a utility function for tests) changes the time for all threads doing tests, and not only for the running test.
Until the false-positive is fixed:
- Use `cargo test -- --test-threads 1` instead of `cargo test`
- Or use `cargo nextest run` (install with `cargo install cargo-nextest --locked`)\n"
)
);
assert!(contact.was_seen_recently());
let self_contact = Contact::get_by_id(&bob, ContactId::SELF).await?;
assert!(!self_contact.was_seen_recently());

View File

@@ -5,12 +5,11 @@
use std::{
cmp::max,
cmp::min,
collections::{BTreeMap, BTreeSet, HashMap},
iter::Peekable,
mem::take,
sync::atomic::Ordering,
time::{Duration, UNIX_EPOCH},
time::Duration,
};
use anyhow::{bail, format_err, Context as _, Result};
@@ -20,8 +19,8 @@ use deltachat_contact_tools::{normalize_name, ContactAddress};
use futures::{FutureExt as _, StreamExt, TryStreamExt};
use futures_lite::FutureExt;
use num_traits::FromPrimitive;
use rand::Rng;
use ratelimit::Ratelimit;
use tokio::sync::RwLock;
use crate::chat::{self, ChatId, ChatIdBlocked};
use crate::chatlist_events;
@@ -43,7 +42,7 @@ use crate::scheduler::connectivity::ConnectivityStore;
use crate::socks::Socks5Config;
use crate::sql;
use crate::stock_str;
use crate::tools::{self, create_id, duration_to_str};
use crate::tools::{create_id, duration_to_str};
pub(crate) mod capabilities;
mod client;
@@ -83,17 +82,15 @@ pub(crate) struct Imap {
pub(crate) connectivity: ConnectivityStore,
conn_last_try: tools::Time,
conn_backoff_ms: u64,
/// Rate limit for successful IMAP connections.
/// Rate limit for IMAP connection attempts.
///
/// This rate limit prevents busy loop in case the server refuses logins
/// This rate limit prevents busy loop
/// in case the server refuses connections
/// or in case connection gets dropped over and over due to IMAP bug,
/// e.g. the server returning invalid response to SELECT command
/// immediately after logging in or returning an error in response to LOGIN command
/// due to internal server error.
ratelimit: Ratelimit,
ratelimit: RwLock<Ratelimit>,
}
#[derive(Debug)]
@@ -251,10 +248,8 @@ impl Imap {
strict_tls,
login_failed_once: false,
connectivity: Default::default(),
conn_last_try: UNIX_EPOCH,
conn_backoff_ms: 0,
// 1 connection per minute + a burst of 2.
ratelimit: Ratelimit::new(Duration::new(120, 0), 2.0),
ratelimit: RwLock::new(Ratelimit::new(Duration::new(120, 0), 2.0)),
};
Ok(imap)
@@ -298,15 +293,7 @@ impl Imap {
bail!("IMAP operation attempted while it is torn down");
}
let now = tools::Time::now();
let until_can_send = max(
min(self.conn_last_try, now)
.checked_add(Duration::from_millis(self.conn_backoff_ms))
.unwrap_or(now),
now,
)
.duration_since(now)?;
let ratelimit_duration = max(until_can_send, self.ratelimit.until_can_send());
let ratelimit_duration = self.ratelimit.read().await.until_can_send();
if !ratelimit_duration.is_zero() {
warn!(
context,
@@ -329,16 +316,7 @@ impl Imap {
info!(context, "Connecting to IMAP server");
self.connectivity.set_connecting(context).await;
self.conn_last_try = tools::Time::now();
const BACKOFF_MIN_MS: u64 = 2000;
const BACKOFF_MAX_MS: u64 = 80_000;
self.conn_backoff_ms = min(self.conn_backoff_ms, BACKOFF_MAX_MS / 2);
self.conn_backoff_ms = self.conn_backoff_ms.saturating_add(
rand::thread_rng().gen_range((self.conn_backoff_ms / 2)..=self.conn_backoff_ms),
);
self.conn_backoff_ms = max(BACKOFF_MIN_MS, self.conn_backoff_ms);
self.ratelimit.write().await.send();
let connection_res: Result<Client> =
if self.lp.security == Socket::Starttls || self.lp.security == Socket::Plain {
let imap_server: &str = self.lp.server.as_ref();
@@ -386,8 +364,6 @@ impl Imap {
}
};
let client = connection_res?;
self.conn_backoff_ms = BACKOFF_MIN_MS;
self.ratelimit.send();
let imap_user: &str = self.lp.user.as_ref();
let imap_pw: &str = self.lp.password.as_ref();

View File

@@ -193,9 +193,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<St
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
};
let private_key_asc = private_key.to_asc(ac_headers);
let encr = pgp::symm_encrypt(passphrase, private_key_asc.as_bytes())
.await?
.replace('\n', "\r\n");
let encr = pgp::symm_encrypt(passphrase, private_key_asc.as_bytes()).await?;
let replacement = format!(
concat!(
@@ -286,7 +284,7 @@ pub async fn continue_key_transfer(
let file = open_file_std(context, filename)?;
let sc = normalize_setup_code(setup_code);
let armored_key = decrypt_setup_file(&sc, file).await?;
set_self_key(context, &armored_key, true).await?;
set_self_key(context, &armored_key, true, true).await?;
maybe_add_bcc_self_device_msg(context).await?;
Ok(())
@@ -295,32 +293,35 @@ pub async fn continue_key_transfer(
}
}
async fn set_self_key(context: &Context, armored: &str, set_default: bool) -> Result<()> {
async fn set_self_key(
context: &Context,
armored: &str,
set_default: bool,
prefer_encrypt_required: bool,
) -> Result<()> {
// try hard to only modify key-state
let (private_key, header) = SignedSecretKey::from_asc(armored)?;
let public_key = private_key.split_public_key()?;
if let Some(preferencrypt) = header.get("Autocrypt-Prefer-Encrypt") {
let e2ee_enabled = match preferencrypt.as_str() {
"nopreference" => 0,
"mutual" => 1,
_ => {
bail!("invalid Autocrypt-Prefer-Encrypt header: {:?}", header);
let preferencrypt = header.get("Autocrypt-Prefer-Encrypt");
match preferencrypt.map(|s| s.as_str()) {
Some(headerval) => {
let e2ee_enabled = match headerval {
"nopreference" => 0,
"mutual" => 1,
_ => {
bail!("invalid Autocrypt-Prefer-Encrypt header: {:?}", header);
}
};
context
.sql
.set_raw_config_int("e2ee_enabled", e2ee_enabled)
.await?;
}
None => {
if prefer_encrypt_required {
bail!("missing Autocrypt-Prefer-Encrypt header");
}
};
context
.sql
.set_raw_config_int("e2ee_enabled", e2ee_enabled)
.await?;
} else {
// `Autocrypt-Prefer-Encrypt` is not included
// in keys exported to file.
//
// `Autocrypt-Prefer-Encrypt` also SHOULD be sent
// in Autocrypt Setup Message according to Autocrypt specification,
// but K-9 6.802 does not include this header.
//
// We keep current setting in this case.
info!(context, "No Autocrypt-Prefer-Encrypt header.");
}
};
let self_addr = context.get_primary_self_addr().await?;
@@ -603,7 +604,7 @@ async fn export_backup_inner(
async fn import_secret_key(context: &Context, path: &Path, set_default: bool) -> Result<()> {
let buf = read_file(context, &path).await?;
let armored = std::string::String::from_utf8_lossy(&buf);
set_self_key(context, &armored, set_default).await?;
set_self_key(context, &armored, set_default, false).await?;
Ok(())
}
@@ -824,7 +825,6 @@ mod tests {
use super::*;
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
use crate::receive_imf::receive_imf;
use crate::stock_str::StockMessage;
use crate::test_utils::{alice_keypair, TestContext, TestContextManager};
@@ -834,17 +834,15 @@ mod tests {
let msg = render_setup_file(&t, "hello").await.unwrap();
println!("{}", &msg);
// Check some substrings, indicating things got substituted.
// In particular note the mixing of `\r\n` and `\n` depending
// on who generated the strings.
assert!(msg.contains("<title>Autocrypt Setup Message</title"));
assert!(msg.contains("<h1>Autocrypt Setup Message</h1>"));
assert!(msg.contains("<p>This is the Autocrypt Setup Message used to"));
assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n"));
assert!(msg.contains("Passphrase-Format: numeric9x4\r\n"));
assert!(msg.contains("Passphrase-Begin: he\r\n"));
assert!(msg.contains("-----END PGP MESSAGE-----\r\n"));
for line in msg.rsplit_terminator('\n') {
assert!(line.ends_with('\r'));
}
assert!(msg.contains("Passphrase-Begin: he\n"));
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -1195,22 +1193,4 @@ mod tests {
Ok(())
}
/// Tests reception of Autocrypt Setup Message from K-9 6.802.
///
/// Unlike Autocrypt Setup Message sent by Delta Chat,
/// this message does not contain `Autocrypt-Prefer-Encrypt` header.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_key_transfer_k_9() -> Result<()> {
let t = &TestContext::new().await;
t.configure_addr("autocrypt@nine.testrun.org").await;
let raw = include_bytes!("../test-data/message/k-9-autocrypt-setup-message.eml");
let received = receive_imf(t, raw, false).await?.unwrap();
let setup_code = "0655-9868-8252-5455-4232-5158-1237-5333-2638";
continue_key_transfer(t, *received.msg_ids.last().unwrap(), setup_code).await?;
Ok(())
}
}

View File

@@ -656,12 +656,6 @@ mod tests {
let text = fs::read_to_string(&path).await.unwrap();
assert_eq!(text, "i am attachment");
let path = path.with_file_name("saved.txt");
msg.save_file(&ctx1, &path).await.unwrap();
let text = fs::read_to_string(&path).await.unwrap();
assert_eq!(text, "i am attachment");
assert!(msg.save_file(&ctx1, &path).await.is_err());
// Check that both received the ImexProgress events.
ctx0.evtracker
.get_matching(|ev| matches!(ev, EventType::ImexProgress(1000)))

View File

@@ -6,7 +6,6 @@ use std::path::{Path, PathBuf};
use anyhow::{ensure, format_err, Context as _, Result};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use tokio::{fs, io};
use crate::blob::BlobObject;
use crate::chat::{Chat, ChatId};
@@ -606,19 +605,6 @@ impl Message {
self.param.get_path(Param::File, context).unwrap_or(None)
}
/// Save file copy at the user-provided path.
pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
let path_src = self.get_file(context).context("No file")?;
let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
let mut dst = fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.await?;
io::copy(&mut src, &mut dst).await?;
Ok(())
}
/// If message is an image or gif, set Param::Width and Param::Height
pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
if self.viewtype.has_file() {
@@ -1057,7 +1043,6 @@ impl Message {
filemime: Option<&str>,
) -> Result<()> {
let blob = BlobObject::create(context, suggested_name, data).await?;
self.param.set(Param::Filename, suggested_name);
self.param.set(Param::File, blob.as_name());
self.param.set_optional(Param::MimeType, filemime);
Ok(())
@@ -1127,7 +1112,7 @@ impl Message {
.get_bool(Param::GuaranteeE2ee)
.unwrap_or_default()
{
self.param.set(Param::ProtectQuote, "1");
self.param.set(Param::GuaranteeE2ee, "1");
}
let text = quote.get_text();
@@ -2015,11 +2000,8 @@ mod tests {
use num_traits::FromPrimitive;
use super::*;
use crate::chat::{
self, add_contact_to_chat, marknoticed_chat, send_text_msg, ChatItem, ProtectionStatus,
};
use crate::chat::{self, marknoticed_chat, send_text_msg, ChatItem};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::reaction::send_reaction;
use crate::receive_imf::receive_imf;
use crate::test_utils as test;
@@ -2043,6 +2025,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_prepare_message_and_send() {
use crate::config::Config;
let d = test::TestContext::new().await;
let ctx = &d.ctx;
@@ -2181,6 +2165,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_quote() {
use crate::config::Config;
let d = test::TestContext::new().await;
let ctx = &d.ctx;
@@ -2213,42 +2199,6 @@ mod tests {
assert_eq!(quoted_msg.get_text(), msg2.quoted_text().unwrap());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_unencrypted_quote_encrypted_message() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let alice_group = alice
.create_group_with_members(ProtectionStatus::Unprotected, "Group chat", &[bob])
.await;
let sent = alice.send_text(alice_group, "Hi! I created a group").await;
let bob_received_message = bob.recv_msg(&sent).await;
let bob_group = bob_received_message.chat_id;
bob_group.accept(bob).await?;
let sent = bob.send_text(bob_group, "Encrypted message").await;
let alice_received_message = alice.recv_msg(&sent).await;
assert!(alice_received_message.get_showpadlock());
// Alice adds contact without key so chat becomes unencrypted.
let alice_flubby_contact_id =
Contact::create(alice, "Flubby", "flubby@example.org").await?;
add_contact_to_chat(alice, alice_group, alice_flubby_contact_id).await?;
// Alice quotes encrypted message in unencrypted chat.
let mut msg = Message::new(Viewtype::Text);
msg.set_quote(alice, Some(&alice_received_message)).await?;
chat::send_msg(alice, alice_group, &mut msg).await?;
let bob_received_message = bob.recv_msg(&alice.pop_sent_msg().await).await;
assert_eq!(bob_received_message.quoted_text().unwrap(), "...");
assert_eq!(bob_received_message.get_showpadlock(), false);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_chat_id() {
// Alice receives a message that pops up as a contact request

View File

@@ -669,19 +669,19 @@ impl<'a> MimeFactory<'a> {
let mut is_gossiped = false;
let peerstates = self.peerstates_for_recipients(context).await?;
let should_encrypt =
encrypt_helper.should_encrypt(context, e2ee_guaranteed, &peerstates)?;
let is_encrypted = should_encrypt && !force_plaintext;
let (main_part, parts) = match self.loaded {
Loaded::Message { .. } => {
self.render_message(context, &mut headers, &grpimage, is_encrypted)
self.render_message(context, &mut headers, &grpimage)
.await?
}
Loaded::Mdn { .. } => (self.render_mdn(context).await?, Vec::new()),
};
let peerstates = self.peerstates_for_recipients(context).await?;
let should_encrypt =
encrypt_helper.should_encrypt(context, e2ee_guaranteed, &peerstates)?;
let is_encrypted = should_encrypt && !force_plaintext;
let message = if parts.is_empty() {
// Single part, render as regular message.
main_part
@@ -960,7 +960,6 @@ impl<'a> MimeFactory<'a> {
context: &Context,
headers: &mut MessageHeaders,
grpimage: &Option<String>,
is_encrypted: bool,
) -> Result<(PartBuilder, Vec<PartBuilder>)> {
let chat = match &self.loaded {
Loaded::Message { chat } => chat,
@@ -1222,16 +1221,6 @@ impl<'a> MimeFactory<'a> {
.msg
.quoted_text()
.map(|quote| format_flowed_quote(&quote) + "\r\n\r\n");
if !is_encrypted
&& self
.msg
.param
.get_bool(Param::ProtectQuote)
.unwrap_or_default()
{
// Message is not encrypted but quotes encrypted message.
quoted_text = Some("> ...\r\n\r\n".to_string());
}
if quoted_text.is_none() && final_text.starts_with('>') {
// Insert empty line to avoid receiver treating user-sent quote as topquote inserted by
// Delta Chat.

View File

@@ -662,34 +662,32 @@ impl MimeMessage {
self.squash_attachment_parts();
}
if !context.get_config_bool(Config::Bot).await? {
if let Some(ref subject) = self.get_subject() {
let mut prepend_subject = true;
if !self.decrypting_failed {
let colon = subject.find(':');
if colon == Some(2)
|| colon == Some(3)
|| self.has_chat_version()
|| subject.contains("Chat:")
{
prepend_subject = false
}
if let Some(ref subject) = self.get_subject() {
let mut prepend_subject = true;
if !self.decrypting_failed {
let colon = subject.find(':');
if colon == Some(2)
|| colon == Some(3)
|| self.has_chat_version()
|| subject.contains("Chat:")
{
prepend_subject = false
}
}
// For mailing lists, always add the subject because sometimes there are different topics
// and otherwise it might be hard to keep track:
if self.is_mailinglist_message() && !self.has_chat_version() {
prepend_subject = true;
}
// For mailing lists, always add the subject because sometimes there are different topics
// and otherwise it might be hard to keep track:
if self.is_mailinglist_message() && !self.has_chat_version() {
prepend_subject = true;
}
if prepend_subject && !subject.is_empty() {
let part_with_text = self
.parts
.iter_mut()
.find(|part| !part.msg.is_empty() && !part.is_reaction);
if let Some(part) = part_with_text {
part.msg = format!("{} {}", subject, part.msg);
}
if prepend_subject && !subject.is_empty() {
let part_with_text = self
.parts
.iter_mut()
.find(|part| !part.msg.is_empty() && !part.is_reaction);
if let Some(part) = part_with_text {
part.msg = format!("{} {}", subject, part.msg);
}
}
}
@@ -3935,31 +3933,4 @@ Content-Disposition: reaction\n\
Ok(())
}
/// Tests that subject is not prepended to the message
/// when bot receives it.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_bot_no_subject() {
let context = TestContext::new().await;
context.set_config(Config::Bot, Some("1")).await.unwrap();
let raw = br#"Message-ID: <foobar@example.org>
From: foo <foo@example.org>
Subject: Some subject
To: bar@example.org
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
/help
"#;
let message = MimeMessage::from_bytes(&context.ctx, &raw[..], None)
.await
.unwrap();
assert_eq!(message.get_subject(), Some("Some subject".to_string()));
assert_eq!(message.parts.len(), 1);
assert_eq!(message.parts[0].typ, Viewtype::Text);
// Not "Some subject /help"
assert_eq!(message.parts[0].msg, "/help");
}
}

View File

@@ -48,11 +48,6 @@ pub enum Param {
/// For Messages: message is encrypted, outgoing: guarantee E2EE or the message is not send
GuaranteeE2ee = b'c',
/// For Messages: quoted message is encrypted.
///
/// If this message is sent unencrypted, quote text should be replaced.
ProtectQuote = b'0',
/// For Messages: decrypted with validation errors or without mutual set, if neither
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
ErroneousE2ee = b'e',

View File

@@ -2994,15 +2994,11 @@ async fn test_long_and_duplicated_filenames() -> Result<()> {
let resulting_filename = msg.get_filename().unwrap();
assert_eq!(resulting_filename, filename);
let path = msg.get_file(t).unwrap();
let path2 = path.with_file_name("saved.txt");
msg.save_file(t, &path2).await.unwrap();
assert!(
path.to_str().unwrap().ends_with(".tar.gz"),
"path {path:?} doesn't end with .tar.gz"
);
assert_eq!(fs::read_to_string(&path).await.unwrap(), content);
assert_eq!(fs::read_to_string(&path2).await.unwrap(), content);
fs::remove_file(path2).await.unwrap();
assert_eq!(fs::read_to_string(path).await.unwrap(), content);
}
check_message(&msg_alice, &alice, filename_sent, &content).await;
check_message(&msg_bob, &bob, filename_sent, &content).await;

View File

@@ -683,12 +683,7 @@ pub(crate) fn buf_decompress(buf: &[u8]) -> Result<Vec<u8>> {
mod tests {
#![allow(clippy::indexing_slicing)]
use chrono::NaiveDate;
use proptest::prelude::*;
use super::*;
use crate::chatlist::Chatlist;
use crate::{chat, test_utils};
use crate::{receive_imf::receive_imf, test_utils::TestContext};
#[test]
@@ -966,6 +961,12 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
assert!(extract_grpid_from_rfc724_mid(mid.as_str()).is_none());
}
use chrono::NaiveDate;
use proptest::prelude::*;
use crate::chatlist::Chatlist;
use crate::{chat, test_utils};
proptest! {
#[test]
fn test_truncate(

View File

@@ -862,7 +862,6 @@ impl Message {
mod tests {
use std::time::Duration;
use regex::Regex;
use serde_json::json;
use super::*;
@@ -1712,6 +1711,8 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_render_webxdc_status_update_object_range() -> Result<()> {
use regex::Regex;
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat").await?;
let instance = send_webxdc_instance(&t, chat_id).await?;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

View File

@@ -1,65 +0,0 @@
Return-Path: <autocrypt@nine.testrun.org>
Delivered-To: autocrypt@nine.testrun.org
Received: from nine.testrun.org
by nine with LMTP
id wNinAKX2J2YWDwEAPdT8mA
(envelope-from <autocrypt@nine.testrun.org>)
for <autocrypt@nine.testrun.org>; Tue, 23 Apr 2024 19:57:57 +0200
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=nine.testrun.org;
s=opendkim; t=1713895076;
bh=yuHuHSbYX5hE/xr8aU2fy/SlqfTL7XjfV2m1eEePTz4=;
h=Subject:Date:From:To:From;
b=ZbVNpJ8zjHmgrCqiRnqzENcR/PwR/G182hL18U5bp5CZmkyWcuhQU0EkhkJpCCv1n
8bZ9WlOT0cmzBHpWU43t7HufuUM56NwwuVqEuz2agpVzQV8zKIPhthrBzbYIeR4Prg
1DgwWr8EhotoV6yPgzxi9sMyO3l4spJeaREisB5MPOIdKeIxtRPLR+Woo5hQWNTFoh
ZQtCcY7w5vxXGhBMVPXOjbrrzOCsE5gGB5QYSAR8Bv3ZdJn/mHvIRCEJG5hJGSxXjQ
fD0UGJ5m5RVrF0tWnZ7U5tpoRD/UVV1+Us9Woq733R97ZchpoE4hNpMG9zYW90z4QU
kBajbsH81Nm0A==
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary=----X2OJUZLGILKJEHMTO29ZMST9701ZDH
Content-Transfer-Encoding: 7bit
Subject: Autocrypt Setup Message
Autocrypt-Setup-Message: v1
Date: Tue, 23 Apr 2024 19:57:57 +0200
From: autocrypt@nine.testrun.org
To: autocrypt@nine.testrun.org
Message-Id: <20240423175756.F19EB17C214A@nine.testrun.org>
------X2OJUZLGILKJEHMTO29ZMST9701ZDH
Content-Type: text/plain;
charset=utf-8
Content-Transfer-Encoding: quoted-printable
This message contains all information to transfer your Autocrypt settings a=
long with your secret key securely from your original device=2E To set up y=
our new device for Autocrypt, please follow the instructions that should be=
presented by your new device=2E You can keep this message and use it as a =
backup for your secret key=2E If you want to do this, you should write down=
the password and store it securely=2E
------X2OJUZLGILKJEHMTO29ZMST9701ZDH
Content-Transfer-Encoding: 7bit
Content-Type: application/autocrypt-setup
Content-Disposition: attachment; filename="autocrypt-setup-message"
-----BEGIN PGP MESSAGE-----
Passphrase-Format: numeric9x4
Passphrase-Begin: 06
ww0ECQMCAhlJ+TRwb2Fg0sGXAUc+92rmg4k57Sd4D3O/SPQNzShbVdlKsoFzyH+B
YhimOr/8C5ZHyg/WjRGlk4pD+t57WfVdE7LYnv8qsK86h2kffZAGlj+B9Lh9+qbV
KgJLpHUKg7ZGa/9aMq7KuFoNSNTbcHtzJ/Ml9GVe+opimER87mpFCjmaEHCcCp0a
ZeS5VU8gTV7AKuPW40BBipyEmKpUvE/ZWfz3KSI4RZyIwM8v8kXBMojT4WLqWm93
JoEKUyeh+3JKMvsfyRbmHXrHprG9f2e8PLvNkAiie68YJniFnwA8nmNSnPv9S9rf
7oUHtnTDKJ4FIpmfPgj1v/KIWWW9KaZWHi7K5mFUCTb4pBoCRIGaFh+JzbSlNL9i
fz7HIiN95bFJ4xXXL4gcU9wO5//npkVDUncaeHhUy1VBLu0NFYvze+s+eAIesqec
X3x++U9d+Slbpa1G2Z5Knj50mBY+k9aNwVMZGu50hzhPvdwesqmbr+GTSh0O1bxI
gw/cDq5s58Ewze3WvYaLxJz/RcwOCGSV8k21FM4WTnEahs4yfLbzNuusYvvciU6l
w0eZC+vEmh+bINSSRX/mcvkQcIkkCsqvfWyxdSNIBCwmR86oalWnxZniBLbbbZHD
0KAsv0w7t00Y715gyyFWyiEiT5Lyl4TA+cUIHKmmpKOaVubz50UD1z5rqT7joJ7G
KRmWtQW8MScgcmK7+tyavLQOxwe8i8i9JkUy+d9jhj17XZil/If26Q3V3epqCXq3
FdvEvvNGJF0DyJ4YAe9QMBumf22sMmX/XVock9/k0pB46mciMhPL3VA=
=LYx9
-----END PGP MESSAGE-----
------X2OJUZLGILKJEHMTO29ZMST9701ZDH--