mirror of
https://github.com/chatmail/core.git
synced 2026-10-03 19:50:33 +03:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91aa8cf259 | ||
|
|
e73107006e | ||
|
|
ca389cc6fc | ||
|
|
60ec7f0cbf | ||
|
|
d342d59e65 | ||
|
|
2690fa2da5 | ||
|
|
e411c394ca | ||
|
|
d69f3ba225 | ||
|
|
739807b1a9 | ||
|
|
d029ea7f3f | ||
|
|
11098cb869 | ||
|
|
f6807d6b22 |
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### API Changes
|
||||
- added APIs to check if database is encrypted and to change the passphrase:
|
||||
`dc_context_is_encrypted()` and `dc_context_change_passphrase()` #3029.
|
||||
|
||||
### Changes
|
||||
- don't watch Sent folder by default #3025
|
||||
- use webxdc app name in chatlist/quotes/replies etc. #3027
|
||||
- refactorings #3023
|
||||
|
||||
|
||||
## 1.72.0
|
||||
|
||||
### Fixes
|
||||
|
||||
Generated
+5
-5
@@ -2122,9 +2122,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.113"
|
||||
version = "0.2.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
|
||||
checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2185,12 +2185,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mailparse"
|
||||
version = "0.13.7"
|
||||
version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d70ae0840b192a2f7d1dc46e75f38720a7e3c52dfdc968ba3202fa270668dc67"
|
||||
checksum = "8cae768a50835557749599277fc59f7c728118724eb34185e8feb633ef266a32"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"charset",
|
||||
"data-encoding",
|
||||
"quoted_printable",
|
||||
]
|
||||
|
||||
|
||||
@@ -241,6 +241,32 @@ int dc_context_open (dc_context_t *context, const char*
|
||||
int dc_context_is_open (dc_context_t *context);
|
||||
|
||||
|
||||
/**
|
||||
* Return 1 if database is encrypted. Can only be checked on open database.
|
||||
* Use this method to decide whether to present an option to change passphrase
|
||||
* to the user.
|
||||
*
|
||||
* @member dc_context_t
|
||||
* @param context The context object.
|
||||
* @return 1 if database is encrypted, 0 if database is not encrypted or on
|
||||
* error.
|
||||
*/
|
||||
int dc_context_is_encrypted (dc_context_t *context);
|
||||
|
||||
|
||||
/**
|
||||
* Changes passphrase for the open database. The database must be encrypted
|
||||
* already, i.e. have a non-empty password. Unencrypted databases can only be
|
||||
* encrypted during import/export.
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param passpharse New passphrase.
|
||||
* @return 1 if database was reencrypted with the new passphrase, 0 on error
|
||||
* (database is closed, database is not encrypted, other SQLCipher error).
|
||||
*/
|
||||
int dc_context_change_passphrase (dc_context_t *context, char *passphrase);
|
||||
|
||||
|
||||
/**
|
||||
* Free a context object.
|
||||
*
|
||||
@@ -335,8 +361,8 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* 1=send a copy of outgoing messages to self.
|
||||
* Sending messages to self is needed for a proper multi-account setup,
|
||||
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
||||
* - `sentbox_watch`= 1=watch `Sent`-folder for changes (default),
|
||||
* 0=do not watch the `Sent`-folder,
|
||||
* - `sentbox_watch`= 1=watch `Sent`-folder for changes,
|
||||
* 0=do not watch the `Sent`-folder (default),
|
||||
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
||||
* - `mvbox_move` = 1=detect chat messages,
|
||||
* move them to the `DeltaChat` folder,
|
||||
|
||||
@@ -144,6 +144,34 @@ pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc:
|
||||
block_on(ctx.is_open()) as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_context_is_encrypted(context: *mut dc_context_t) -> libc::c_int {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_context_is_encrypted()");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let ctx = &*context;
|
||||
block_on(ctx.is_encrypted()) as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_context_change_passphrase(
|
||||
context: *mut dc_context_t,
|
||||
passphrase: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
if context.is_null() {
|
||||
eprintln!("ignoring careless call to dc_context_change_passphrase()");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let ctx = &*context;
|
||||
let passphrase = to_string_lossy(passphrase);
|
||||
block_on(ctx.change_passphrase(passphrase))
|
||||
.log_err(ctx, "change_passphrase failed")
|
||||
.is_ok() as libc::c_int
|
||||
}
|
||||
|
||||
/// Release the context structure.
|
||||
///
|
||||
/// This function releases the memory of the `dc_context_t` structure.
|
||||
|
||||
@@ -68,38 +68,46 @@ The callback is called for updates sent by you or other peers.
|
||||
|
||||
### getAllUpdates()
|
||||
|
||||
```
|
||||
payloads = window.webxdc.getAllUpdates()
|
||||
```js
|
||||
updates = await window.webxdc.getAllUpdates();
|
||||
```
|
||||
|
||||
In case your Webxdc was just started,
|
||||
you may want to reconstruct the state from the last run -
|
||||
and also incorporate updates that may have arrived while the app was not running.
|
||||
|
||||
- `payloads`: the function returns all previous updates in an array,
|
||||
- `updates`: All previous updates in an array,
|
||||
eg. `[{payload: "foo"},{payload: "bar"}]`
|
||||
if `webxdc.sendUpdate("foo"); webxdc.sendUpdate("bar");` was called on the last run.
|
||||
if `webxdc.sendUpdate({payload: "foo"}); webxdc.sendUpdate({payload: "bar"};` was called on the last run.
|
||||
|
||||
|
||||
### selfAddr()
|
||||
The updates are wrapped into a Promise that you can `await` for.
|
||||
If you are not in an async function and cannot use `await` therefore,
|
||||
you can get the updates with `then()`:
|
||||
|
||||
```js
|
||||
addr = window.webxdc.selfAddr()
|
||||
window.webxdc.getAllUpdates().then(updates => {});
|
||||
```
|
||||
|
||||
Returns the peer's own address.
|
||||
|
||||
### selfAddr
|
||||
|
||||
```js
|
||||
window.webxdc.selfAddr
|
||||
```
|
||||
|
||||
Property with the peer's own address.
|
||||
This is esp. useful if you want to differ between different peers -
|
||||
just send the address along with the payload,
|
||||
and, if needed, compare the payload addresses against selfAddr() later on.
|
||||
|
||||
|
||||
### selfName()
|
||||
### selfName
|
||||
|
||||
```js
|
||||
addr = window.webxdc.selfName()
|
||||
window.webxdc.selfName
|
||||
```
|
||||
|
||||
Returns the peer's own name.
|
||||
Property with the peer's own name.
|
||||
This is name chosen by the user in their settings,
|
||||
if there is nothing set, that defaults to the peer's address.
|
||||
|
||||
@@ -128,7 +136,7 @@ round corners etc. will be added by the implementations as needed.
|
||||
If no icon is set, a default icon will be used.
|
||||
|
||||
|
||||
## Webxdc Example
|
||||
## Webxdc Examples
|
||||
|
||||
The following example shows an input field and every input is show on all peers.
|
||||
|
||||
@@ -155,14 +163,26 @@ The following example shows an input field and every input is show on all peers
|
||||
}
|
||||
|
||||
window.webxdc.setUpdateListener(receiveUpdate);
|
||||
window.webxdc.getAllUpdates().forEach(receiveUpdate);
|
||||
window.webxdc.getAllUpdates().then(updates => updates.forEach(receiveUpdate));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
For a more advanved example, see https://github.com/r10s/webxdc-poll/ .
|
||||
[Webxdc Development Tool](https://github.com/deltachat/webxdc-dev)
|
||||
offers an **Webxdc Simulator** that can be used in many browsers without any installation needed.
|
||||
You can also use that repository as a template for your own app -
|
||||
just clone and start adapting things to your need.
|
||||
|
||||
|
||||
### Advanced Examples
|
||||
|
||||
- [2048](https://github.com/adbenitez/2048.xdc)
|
||||
- [Draw](https://github.com/adbenitez/draw.xdc)
|
||||
- [Poll](https://github.com/r10s/webxdc-poll/)
|
||||
- [Tic Tac Toe](https://github.com/Simon-Laux/tictactoe.xdc)
|
||||
- Even more with [Topic #webxdc on Github](https://github.com/topics/webxdc)
|
||||
|
||||
|
||||
## Closing Remarks
|
||||
|
||||
+15
-2
@@ -65,7 +65,7 @@ pub enum Config {
|
||||
#[strum(props(default = "1"))]
|
||||
MdnsEnabled,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
#[strum(props(default = "0"))]
|
||||
SentboxWatch,
|
||||
|
||||
#[strum(props(default = "1"))]
|
||||
@@ -309,7 +309,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub async fn set_config_bool(&self, key: Config, value: bool) -> Result<()> {
|
||||
self.set_config(key, if value { Some("1") } else { None })
|
||||
self.set_config(key, if value { Some("1") } else { Some("0") })
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -401,4 +401,17 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for https://github.com/deltachat/deltachat-core-rust/issues/3012
|
||||
#[async_std::test]
|
||||
async fn test_set_config_bool() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// We need some config that defaults to true
|
||||
let c = Config::E2eeEnabled;
|
||||
assert_eq!(t.get_config_bool(c).await?, true);
|
||||
t.set_config_bool(c, false).await?;
|
||||
assert_eq!(t.get_config_bool(c).await?, false);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,12 @@ impl Context {
|
||||
self.sql.is_open().await
|
||||
}
|
||||
|
||||
/// Returns true if database is encrypted. Returns false if database is not open yet or not
|
||||
/// encrypted.
|
||||
pub async fn is_encrypted(&self) -> bool {
|
||||
self.sql.is_encrypted().await.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Tests the database passphrase.
|
||||
///
|
||||
/// Returns true if passphrase is correct.
|
||||
@@ -155,6 +161,14 @@ impl Context {
|
||||
self.sql.check_passphrase(passphrase).await
|
||||
}
|
||||
|
||||
/// Changes the database passphrase.
|
||||
///
|
||||
/// Works only for encrypted databases. Encrypted database can only be converted to unencrypted
|
||||
/// one and backwards via import/export.
|
||||
pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
|
||||
self.sql.change_passphrase(self, passphrase).await
|
||||
}
|
||||
|
||||
pub(crate) async fn with_blobdir(
|
||||
dbfile: PathBuf,
|
||||
blobdir: PathBuf,
|
||||
@@ -1069,4 +1083,34 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_change_passphrase() -> Result<()> {
|
||||
let dir = tempdir()?;
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
|
||||
let id = 1;
|
||||
let context = Context::new_closed(dbfile.clone().into(), id)
|
||||
.await
|
||||
.context("failed to create context")?;
|
||||
assert_eq!(context.open("foo".to_string()).await?, true);
|
||||
assert_eq!(context.is_open().await, true);
|
||||
assert_eq!(context.is_encrypted().await, true);
|
||||
|
||||
context.change_passphrase("bar".to_string()).await?;
|
||||
drop(context);
|
||||
|
||||
let id = 2;
|
||||
let context = Context::new(dbfile.into(), id)
|
||||
.await
|
||||
.context("failed to create context")?;
|
||||
assert_eq!(context.is_open().await, false);
|
||||
assert_eq!(context.check_passphrase("foo".to_string()).await?, false);
|
||||
assert_eq!(context.check_passphrase("bar".to_string()).await?, true);
|
||||
assert_eq!(context.open("foo".to_string()).await?, false);
|
||||
assert_eq!(context.open("bar".to_string()).await?, true);
|
||||
assert_eq!(context.is_encrypted().await, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+7
-36
@@ -4,10 +4,10 @@ pub mod send;
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{format_err, Context as _};
|
||||
use anyhow::{bail, format_err, Context as _, Result};
|
||||
use async_smtp::smtp::client::net::ClientTlsParameters;
|
||||
use async_smtp::smtp::response::{Category, Code, Detail};
|
||||
use async_smtp::{error, smtp, EmailAddress, ServerAddress};
|
||||
use async_smtp::{smtp, EmailAddress, ServerAddress};
|
||||
|
||||
use crate::constants::DC_LP_AUTH_OAUTH2;
|
||||
use crate::events::EventType;
|
||||
@@ -23,28 +23,6 @@ use crate::{context::Context, scheduler::connectivity::ConnectivityStore};
|
||||
/// SMTP write and read timeout in seconds.
|
||||
const SMTP_TIMEOUT: u64 = 30;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Bad parameters")]
|
||||
BadParameters,
|
||||
#[error("Invalid login address {address}: {error}")]
|
||||
InvalidLoginAddress {
|
||||
address: String,
|
||||
#[source]
|
||||
error: error::Error,
|
||||
},
|
||||
#[error("SMTP failed to connect: {0}")]
|
||||
ConnectionFailure(#[source] smtp::error::Error),
|
||||
#[error("SMTP oauth2 error {address}")]
|
||||
Oauth2 { address: String },
|
||||
#[error("TLS error {0}")]
|
||||
Tls(#[from] async_native_tls::Error),
|
||||
#[error("{0}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Smtp {
|
||||
transport: Option<smtp::SmtpTransport>,
|
||||
@@ -135,14 +113,11 @@ impl Smtp {
|
||||
}
|
||||
|
||||
if lp.server.is_empty() || lp.port == 0 {
|
||||
return Err(Error::BadParameters);
|
||||
bail!("bad connection parameters");
|
||||
}
|
||||
|
||||
let from =
|
||||
EmailAddress::new(addr.to_string()).map_err(|err| Error::InvalidLoginAddress {
|
||||
address: addr.to_string(),
|
||||
error: err,
|
||||
})?;
|
||||
let from = EmailAddress::new(addr.to_string())
|
||||
.with_context(|| format!("invalid login address {}", addr))?;
|
||||
|
||||
self.from = Some(from);
|
||||
|
||||
@@ -163,9 +138,7 @@ impl Smtp {
|
||||
let send_pw = &lp.password;
|
||||
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false).await?;
|
||||
if access_token.is_none() {
|
||||
return Err(Error::Oauth2 {
|
||||
address: addr.to_string(),
|
||||
});
|
||||
bail!("SMTP OAuth 2 error {}", addr);
|
||||
}
|
||||
let user = &lp.user;
|
||||
(
|
||||
@@ -209,9 +182,7 @@ impl Smtp {
|
||||
}
|
||||
|
||||
let mut trans = client.into_transport();
|
||||
if let Err(err) = trans.connect().await {
|
||||
return Err(Error::ConnectionFailure(err));
|
||||
}
|
||||
trans.connect().await.context("SMTP failed to connect")?;
|
||||
|
||||
self.transport = Some(trans);
|
||||
self.last_success = Some(SystemTime::now());
|
||||
|
||||
+34
@@ -97,6 +97,40 @@ impl Sql {
|
||||
*self.is_encrypted.read().await
|
||||
}
|
||||
|
||||
/// Changes the database passpharse.
|
||||
///
|
||||
/// The database must be open and encrypted already.
|
||||
pub(crate) async fn change_passphrase(
|
||||
&self,
|
||||
context: &Context,
|
||||
passphrase: String,
|
||||
) -> Result<()> {
|
||||
// Take the whole pool so nobody opens another connection in parallel.
|
||||
let pool = self
|
||||
.pool
|
||||
.write()
|
||||
.await
|
||||
.take()
|
||||
.context("the database must be open before rekeying")?;
|
||||
|
||||
// Get one connection and rekey the database.
|
||||
// All other connections will stop working after that.
|
||||
let connection = pool
|
||||
.get()
|
||||
.context("failed to get connection from the pool")?;
|
||||
connection
|
||||
.pragma_update(None, "rekey", &passphrase)
|
||||
.context("failed to set PRAGMA rekey")?;
|
||||
drop(pool);
|
||||
|
||||
// Reopen the database with new passphrase.
|
||||
self.open(context, passphrase)
|
||||
.await
|
||||
.context("failed to reopen the database after rekeying")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Closes all underlying Sqlite connections.
|
||||
async fn close(&self) {
|
||||
let _ = self.pool.write().await.take();
|
||||
|
||||
+4
-1
@@ -139,7 +139,10 @@ impl Message {
|
||||
}
|
||||
Viewtype::Webxdc => {
|
||||
append_text = true;
|
||||
"Webxdc".to_string()
|
||||
self.get_webxdc_info(context)
|
||||
.await
|
||||
.map(|info| info.name)
|
||||
.unwrap_or_else(|_| "ErrWebxdcName".to_string())
|
||||
}
|
||||
Viewtype::Text | Viewtype::Unknown => {
|
||||
if self.param.get_cmd() != SystemMessage::LocationOnly {
|
||||
|
||||
@@ -488,6 +488,7 @@ mod tests {
|
||||
add_contact_to_chat, create_group_chat, forward_msgs, send_msg, send_text_msg, ChatId,
|
||||
ProtectionStatus,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::contact::Contact;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::test_utils::TestContext;
|
||||
@@ -1514,4 +1515,24 @@ sth_for_the = "future""#
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_webxdc_chatlist_summary() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"with-minimal-manifest.xdc",
|
||||
include_bytes!("../test-data/webxdc/with-minimal-manifest.xdc"),
|
||||
)
|
||||
.await?;
|
||||
send_msg(&t, chat_id, &mut instance).await?;
|
||||
|
||||
let chatlist = Chatlist::try_load(&t, 0, None, None).await?;
|
||||
assert_eq!(chatlist.len(), 1);
|
||||
let summary = chatlist.get_summary(&t, 0, None).await?;
|
||||
assert_eq!(summary.text, "nice app!".to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user