Compare commits

..

5 Commits

Author SHA1 Message Date
link2xt
33134255ae Test windows with Rust 1.77 2024-05-12 02:19:06 +00:00
link2xt
b8c3b4593c Revert "ci: pin nextest version"
This reverts commit d5af7b05d0.
2024-05-12 02:18:44 +00:00
link2xt
d5af7b05d0 ci: pin nextest version 2024-05-12 01:19:14 +00:00
link2xt
aad7a82549 Revert "Try to use test instead of nextest"
This reverts commit fab6341dbb.
2024-05-12 01:16:04 +00:00
link2xt
fab6341dbb Try to use test instead of nextest 2024-05-12 01:13:02 +00:00
50 changed files with 196 additions and 1092 deletions

View File

@@ -85,7 +85,7 @@ jobs:
- os: ubuntu-latest
rust: 1.78.0
- os: windows-latest
rust: 1.78.0
rust: 1.77.0
- os: macos-latest
rust: 1.78.0
@@ -113,9 +113,6 @@ jobs:
- name: Tests
env:
RUST_BACKTRACE: 1
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
run: cargo nextest run --workspace
- name: Doc-Tests

View File

@@ -271,18 +271,13 @@ jobs:
name: Build & Publish npm prebuilds and deltachat-rpc-server
needs: ["build_linux", "build_windows", "build_macos"]
runs-on: "ubuntu-latest"
permissions:
id-token: write
# Needed to publish the binaries to the release.
contents: write
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: '3.11'
- name: Download Linux aarch64 binary
uses: actions/download-artifact@v4
@@ -373,7 +368,7 @@ jobs:
for platform in ./platform_package/*; do npm pack "$platform"; done
npm pack
ls -lah
- name: Upload to artifacts
uses: actions/upload-artifact@v4
with:
@@ -388,19 +383,11 @@ jobs:
run: |
gh release upload ${{ github.ref_name }} \
--repo ${{ github.repository }} \
deltachat-rpc-server/npm-package/*.tgz
# Configure Node.js for publishing.
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
*.tgz
- name: Publish npm packets for prebuilds and `@deltachat/stdio-rpc-server`
if: github.event_name == 'release'
working-directory: deltachat-rpc-server/npm-package
run: |
ls -lah platform_package
for platform in *.tgz; do npm publish --provenance "$platform"; done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
for platform in *.tgz; do npm publish "$platform"; done

View File

@@ -1,181 +1,5 @@
# Changelog
## [1.138.5] - 2024-05-16
### API-Changes
- jsonrpc: Add parse_vcard() ([#5202](https://github.com/deltachat/deltachat-core-rust/pull/5202)).
- Add Viewtype::Vcard ([#5202](https://github.com/deltachat/deltachat-core-rust/pull/5202)).
- Add make_vcard() ([#5203](https://github.com/deltachat/deltachat-core-rust/pull/5203)).
### Build system
- Add repository URL to deltachat-rpc-server packages.
### Fixes
- Parsing vCards with avatars exported by Android's "Contacts" app.
### Miscellaneous Tasks
- Rebuild node constants.
### Refactor
- contact-tools: VcardContact: rename display_name to authname.
- VcardContact: Change timestamp type to i64.
## [1.138.4] - 2024-05-15
### CI
- Run actions/setup-node before npm publish.
## [1.138.3] - 2024-05-15
### CI
- Give CI job permission to publish binaries to the release.
## [1.138.2] - 2024-05-15
### API-Changes
- deltachat-rpc-client: Add CONFIG_SYNCED constant.
### CI
- Add npm token to publish deltachat-rpc-server packages.
### Features / Changes
- Reset more settings when configuring a chatmail account.
### Tests
- Set configuration after configure() finishes.
## [1.138.1] - 2024-05-14
### Features / Changes
- Detect XCHATMAIL capability and expose it as `is_chatmail` config.
### Fixes
- Never treat message with Chat-Group-ID as a private reply.
- Always prefer Chat-Group-ID over In-Reply-To and References.
- Ignore parent message if message references itself.
### CI
- Set RUSTUP_WINDOWS_PATH_ADD_BIN to work around `nextest` issue <https://github.com/nextest-rs/nextest/issues/1493>.
- deltachat-rpc-server: Fix upload of npm packages to github releases ([#5564](https://github.com/deltachat/deltachat-core-rust/pull/5564)).
### Refactor
- Add MimeMessage.get_chat_group_id().
- Make MimeMessage.get_header() return Option<&str>.
- sql: Make open flags immutable.
- Resultify token::lookup_or_new().
### Miscellaneous Tasks
- cargo: Bump parking_lot from 0.12.1 to 0.12.2.
- cargo: Bump libc from 0.2.153 to 0.2.154.
- cargo: Bump hickory-resolver from 0.24.0 to 0.24.1.
- cargo: Bump serde_json from 1.0.115 to 1.0.116.
- cargo: Bump human-panic from 1.2.3 to 2.0.0.
- cargo: Bump brotli from 5.0.0 to 6.0.0.
## [1.138.0] - 2024-05-13
### API-Changes
- Add dc_msg_save_file() which saves file copy at the provided path ([#4309](https://github.com/deltachat/deltachat-core-rust/pull/4309)).
- Api!(jsonrpc): replace EphemeralTimer tag "variant" with "kind"
### CI
- Use rsync instead of 3rd party github action.
- Replace `black` with `ruff format`.
- Update Rust to 1.78.0.
### Documentation
- Fix references in Message.set_location() documentation.
- Remove Doxygen markup from Message.has_location().
- Add `location` module documentation.
### Features / Changes
- Delete expired path locations in ephemeral loop.
- Delete orphaned POI locations during housekeeping.
- Parsing vCards for contacts sharing ([#5482](https://github.com/deltachat/deltachat-core-rust/pull/5482)).
- contact-tools: Support parsing profile images from "PHOTO:data:image/jpeg;base64,...".
- contact-tools: Add make_vcard().
- Do not add location markers to messages with non-POI location.
- Make one-to-one chats read-only the first seconds of a SecureJoin ([#5512](https://github.com/deltachat/deltachat-core-rust/pull/5512)).
### Fixes
- Message::set_file_from_bytes(): Set Param::Filename.
- Do not fail to send encrypted quotes to unencrypted chats.
- Never prepend subject to message text when bot receives it.
- Interrupt location loop when new location is stored.
- Correct message viewtype before recoding image blob ([#5496](https://github.com/deltachat/deltachat-core-rust/pull/5496)).
- Delete POI location when disappearing message expires.
- Delete non-POI locations after `delete_device_after`, not immediately.
- Update special chats icons even if they are blocked ([#5509](https://github.com/deltachat/deltachat-core-rust/pull/5509)).
- Use ChatIdBlocked::lookup_by_contact() instead of ChatId's method when applicable.
### Miscellaneous Tasks
- cargo: Bump quote from 1.0.35 to 1.0.36.
- cargo: Bump base64 from 0.22.0 to 0.22.1.
- cargo: Bump serde from 1.0.197 to 1.0.200.
- cargo: Bump async-channel from 2.2.0 to 2.2.1.
- cargo: Bump thiserror from 1.0.58 to 1.0.59.
- cargo: Bump anyhow from 1.0.81 to 1.0.82.
- cargo: Bump chrono from 0.4.37 to 0.4.38.
- cargo: Bump imap-proto from 0.16.4 to 0.16.5.
- cargo: Bump syn from 2.0.57 to 2.0.60.
- cargo: Bump mailparse from 0.14.1 to 0.15.0.
- cargo: Bump schemars from 0.8.16 to 0.8.19.
### Other
- Build ts docs with ci + nix.
- Push docs to delta.chat instead of codespeak
- Implement jsonrpc-docs build in github action
- Rm unneeded rust install from ts docs ci
- Correct folder for js.jsonrpc docs
- Add npm install to upload-docs.yml
- Add : to upload-docs.yml
- Upload-docs npm run => npm run build
- Rm leading slash
- Rm npm install
- Merge pull request #5515 from deltachat/dependabot/cargo/quote-1.0.36
- Merge pull request #5522 from deltachat/dependabot/cargo/chrono-0.4.38
- Merge pull request #5523 from deltachat/dependabot/cargo/mailparse-0.15.0
- Add webxdc internal integration commands in jsonrpc ([#5541](https://github.com/deltachat/deltachat-core-rust/pull/5541))
- Limit quote replies ([#5543](https://github.com/deltachat/deltachat-core-rust/pull/5543))
- Stdio jsonrpc server npm package ([#5332](https://github.com/deltachat/deltachat-core-rust/pull/5332))
### Refactor
- python: Fix ruff 0.4.2 warnings.
- Move `delete_poi_location` to location module and document it.
- Remove allow_keychange.
### Tests
- Explain test_was_seen_recently false-positive and give workaround instructions ([#5474](https://github.com/deltachat/deltachat-core-rust/pull/5474)).
- Test that member is added even if "Member added" is lost.
- Test that POIs are deleted when ephemeral message expires.
- Test ts build on branch
## [1.137.4] - 2024-04-24
### API-Changes
@@ -4159,9 +3983,3 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[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
[1.138.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.137.4...v1.138.0
[1.138.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.0...v1.138.1
[1.138.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.1...v1.138.2
[1.138.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.2...v1.138.3
[1.138.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.3...v1.138.4
[1.138.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.138.4...v1.138.5

35
Cargo.lock generated
View File

@@ -567,9 +567,9 @@ dependencies = [
[[package]]
name = "brotli"
version = "6.0.0"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -1157,7 +1157,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.138.5"
version = "1.137.4"
dependencies = [
"ansi_term",
"anyhow",
@@ -1250,14 +1250,13 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.138.5"
version = "1.137.4"
dependencies = [
"anyhow",
"async-channel 2.2.1",
"axum",
"base64 0.22.1",
"deltachat",
"deltachat-contact-tools",
"env_logger 0.11.3",
"futures",
"log",
@@ -1275,7 +1274,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.138.5"
version = "1.137.4"
dependencies = [
"ansi_term",
"anyhow",
@@ -1290,7 +1289,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.138.5"
version = "1.137.4"
dependencies = [
"anyhow",
"deltachat",
@@ -1319,7 +1318,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.138.5"
version = "1.137.4"
dependencies = [
"anyhow",
"deltachat",
@@ -2364,9 +2363,9 @@ dependencies = [
[[package]]
name = "hickory-resolver"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243"
checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
dependencies = [
"cfg-if",
"futures-util",
@@ -2469,9 +2468,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "human-panic"
version = "2.0.0"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c5d0e9120f6bca6120d142c7ede1ba376dd6bf276d69dd3dbe6cbeb7824179"
checksum = "c4f016c89920bbb30951a8405ecacbb4540db5524313b9445736e7e1855cf370"
dependencies = [
"backtrace",
"os_info",
@@ -2836,9 +2835,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.154"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libm"
@@ -3399,9 +3398,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "parking_lot"
version = "0.12.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -4551,9 +4550,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.116"
version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
dependencies = [
"itoa",
"ryu",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.138.5"
version = "1.137.4"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.77"
@@ -47,7 +47,7 @@ async-smtp = { version = "0.9", default-features = false, features = ["runtime-t
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
backtrace = "0.3"
base64 = "0.22"
brotli = { version = "6", default-features=false, features = ["std"] }
brotli = { version = "5", default-features=false, features = ["std"] }
chrono = { workspace = true }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }

View File

@@ -44,25 +44,14 @@ use regex::Regex;
pub struct VcardContact {
/// The email address, vcard property `email`
pub addr: String,
/// This must be the name authorized by the contact itself, not a locally given name. Vcard
/// property `fn`. Can be empty, one should use `display_name()` to obtain the display name.
pub authname: String,
/// The contact's display name, vcard property `fn`
pub display_name: String,
/// The contact's public PGP key in Base64, vcard property `key`
pub key: Option<String>,
/// The contact's profile image (=avatar) in Base64, vcard property `photo`
pub profile_image: Option<String>,
/// The timestamp when the vcard was created / last updated, vcard property `rev`
pub timestamp: Result<i64>,
}
impl VcardContact {
/// Returns the contact's display name.
pub fn display_name(&self) -> &str {
match self.authname.is_empty() {
false => &self.authname,
true => &self.addr,
}
}
pub timestamp: Result<u64>,
}
/// Returns a vCard containing given contacts.
@@ -71,6 +60,7 @@ impl VcardContact {
pub fn make_vcard(contacts: &[VcardContact]) -> String {
fn format_timestamp(c: &VcardContact) -> Option<String> {
let timestamp = *c.timestamp.as_ref().ok()?;
let timestamp: i64 = timestamp.try_into().ok()?;
let datetime = DateTime::from_timestamp(timestamp, 0)?;
Some(datetime.format("%Y%m%dT%H%M%SZ").to_string())
}
@@ -78,7 +68,10 @@ pub fn make_vcard(contacts: &[VcardContact]) -> String {
let mut res = "".to_string();
for c in contacts {
let addr = &c.addr;
let display_name = c.display_name();
let display_name = match c.display_name.is_empty() {
false => &c.display_name,
true => &c.addr,
};
res += &format!(
"BEGIN:VCARD\n\
VERSION:4.0\n\
@@ -100,7 +93,7 @@ pub fn make_vcard(contacts: &[VcardContact]) -> String {
}
/// Parses `VcardContact`s from a given `&str`.
pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
pub fn parse_vcard(vcard: &str) -> Result<Vec<VcardContact>> {
fn remove_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
let start_of_s = s.get(..prefix.len())?;
@@ -132,7 +125,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
}
Some(value)
}
fn parse_datetime(datetime: &str) -> Result<i64> {
fn parse_datetime(datetime: &str) -> Result<u64> {
// According to https://www.rfc-editor.org/rfc/rfc6350#section-4.3.5, the timestamp
// is in ISO.8601.2004 format. DateTime::parse_from_rfc3339() apparently parses
// ISO.8601, but fails to parse any of the examples given.
@@ -151,14 +144,10 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
Err(_) => return Err(e.into()),
},
};
Ok(timestamp)
Ok(timestamp.try_into()?)
}
// Remove line folding, see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2
static NEWLINE_AND_SPACE_OR_TAB: Lazy<Regex> = Lazy::new(|| Regex::new("\n[\t ]").unwrap());
let unfolded_lines = NEWLINE_AND_SPACE_OR_TAB.replace_all(vcard, "");
let mut lines = unfolded_lines.lines().peekable();
let mut lines = vcard.lines().peekable();
let mut contacts = Vec::new();
while lines.peek().is_some() {
@@ -186,11 +175,8 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
{
key.get_or_insert(k);
} else if let Some(p) = remove_prefix(line, "PHOTO;JPEG;ENCODING=BASE64:")
.or_else(|| remove_prefix(line, "PHOTO;ENCODING=BASE64;JPEG:"))
.or_else(|| remove_prefix(line, "PHOTO;TYPE=JPEG;ENCODING=b:"))
.or_else(|| remove_prefix(line, "PHOTO;ENCODING=b;TYPE=JPEG:"))
.or_else(|| remove_prefix(line, "PHOTO;ENCODING=BASE64;TYPE=JPEG:"))
.or_else(|| remove_prefix(line, "PHOTO;TYPE=JPEG;ENCODING=BASE64:"))
.or_else(|| remove_prefix(line, "PHOTO:data:image/jpeg;base64,"))
{
photo.get_or_insert(p);
@@ -201,11 +187,11 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
}
}
let (authname, addr) =
let (display_name, addr) =
sanitize_name_and_addr(display_name.unwrap_or(""), addr.unwrap_or(""));
contacts.push(VcardContact {
authname,
display_name,
addr,
key: key.map(|s| s.to_string()),
profile_image: photo.map(|s| s.to_string()),
@@ -215,7 +201,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
});
}
contacts
Ok(contacts)
}
/// Valid contact address.
@@ -444,16 +430,17 @@ EMAIL;PREF=1:bobzzz@freenet.de
UID:cac4fef4-6351-4854-bbe4-9b6df857eaed
END:VCARD
",
);
)
.unwrap();
assert_eq!(contacts[0].addr, "alice.mueller@posteo.de".to_string());
assert_eq!(contacts[0].authname, "Alice Mueller".to_string());
assert_eq!(contacts[0].display_name, "Alice Mueller".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image, None);
assert!(contacts[0].timestamp.is_err());
assert_eq!(contacts[1].addr, "bobzzz@freenet.de".to_string());
assert_eq!(contacts[1].authname, "".to_string());
assert_eq!(contacts[1].display_name, "".to_string());
assert_eq!(contacts[1].key, None);
assert_eq!(contacts[1].profile_image, None);
assert!(contacts[1].timestamp.is_err());
@@ -474,10 +461,11 @@ KEY;TYPE=PGP;ENCODING=b:[base64-data]
REV:20240418T184242Z
END:VCARD",
);
)
.unwrap();
assert_eq!(contacts[0].addr, "alice@example.com".to_string());
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
assert_eq!(contacts[0].display_name, "Alice Wonderland".to_string());
assert_eq!(contacts[0].key, Some("[base64-data]".to_string()));
assert_eq!(contacts[0].profile_image, None);
assert_eq!(*contacts[0].timestamp.as_ref().unwrap(), 1713465762);
@@ -490,48 +478,27 @@ END:VCARD",
let contacts = [
VcardContact {
addr: "alice@example.org".to_string(),
authname: "Alice Wonderland".to_string(),
display_name: "Alice Wonderland".to_string(),
key: Some("[base64-data]".to_string()),
profile_image: Some("image in Base64".to_string()),
timestamp: Ok(1713465762),
},
VcardContact {
addr: "bob@example.com".to_string(),
authname: "".to_string(),
display_name: "".to_string(),
key: None,
profile_image: None,
timestamp: Ok(0),
},
];
let items = [
"BEGIN:VCARD\n\
VERSION:4.0\n\
EMAIL:alice@example.org\n\
FN:Alice Wonderland\n\
KEY:data:application/pgp-keys;base64,[base64-data]\n\
PHOTO:data:image/jpeg;base64,image in Base64\n\
REV:20240418T184242Z\n\
END:VCARD\n",
"BEGIN:VCARD\n\
VERSION:4.0\n\
EMAIL:bob@example.com\n\
FN:bob@example.com\n\
REV:19700101T000000Z\n\
END:VCARD\n",
];
let mut expected = "".to_string();
for len in 0..=contacts.len() {
let contacts = &contacts[0..len];
let vcard = make_vcard(contacts);
if len > 0 {
expected += items[len - 1];
}
assert_eq!(vcard, expected);
let parsed = parse_vcard(&vcard);
let parsed = parse_vcard(&vcard).unwrap();
assert_eq!(parsed.len(), contacts.len());
for i in 0..parsed.len() {
assert_eq!(parsed[i].addr, contacts[i].addr);
assert_eq!(parsed[i].authname, contacts[i].authname);
assert_eq!(parsed[i].display_name, contacts[i].display_name);
assert_eq!(parsed[i].key, contacts[i].key);
assert_eq!(parsed[i].profile_image, contacts[i].profile_image);
assert_eq!(
@@ -605,15 +572,16 @@ FN:Alice
EMAIL;HOME:alice@example.org
END:VCARD
",
);
)
.unwrap();
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(contacts[0].display_name, "Bob".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image, None);
assert_eq!(contacts[1].addr, "alice@example.org".to_string());
assert_eq!(contacts[1].authname, "Alice".to_string());
assert_eq!(contacts[1].display_name, "Alice".to_string());
assert_eq!(contacts[1].key, None);
assert_eq!(contacts[1].profile_image, None);
@@ -629,40 +597,19 @@ END:VCARD
EMAIL;TYPE=work:alice@example.org\n\
REV:20240418T184242\n\
END:VCARD",
);
)
.unwrap();
assert_eq!(contacts.len(), 1);
assert_eq!(contacts[0].addr, "alice@example.org".to_string());
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
assert_eq!(contacts[0].display_name, "Alice Wonderland".to_string());
assert_eq!(
*contacts[0].timestamp.as_ref().unwrap(),
chrono::offset::Local
.with_ymd_and_hms(2024, 4, 18, 18, 42, 42)
.unwrap()
.timestamp()
.try_into()
.unwrap()
);
}
#[test]
fn test_android_vcard_with_base64_avatar() {
// This is not an actual base64-encoded avatar, it's just to test the parsing
let contacts = parse_vcard(
"BEGIN:VCARD
VERSION:2.1
N:;Bob;;;
FN:Bob
EMAIL;HOME:bob@example.org
PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU
AAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAA
L8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==
END:VCARD
",
);
assert_eq!(contacts.len(), 1);
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==");
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.138.5"
version = "1.137.4"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
@@ -17,7 +17,7 @@ crate-type = ["cdylib", "staticlib"]
deltachat = { path = "../", default-features = false }
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", optional = true }
libc = "0.2"
human-panic = { version = "2", default-features = false }
human-panic = { version = "1", default-features = false }
num-traits = "0.2"
serde_json = "1.0"
tokio = { version = "1.37.0", features = ["rt-multi-thread"] }

View File

@@ -517,7 +517,6 @@ char* dc_get_blobdir (const dc_context_t* context);
* 0=Nothing else happens when the key changes.
* 1=After the key changed, `dc_chat_can_send()` returns false and `dc_chat_is_protection_broken()` returns true
* until `dc_accept_chat()` is called.
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
* - `ui.*` = All keys prefixed by `ui.` can be used by the user-interfaces for system-specific purposes.
* The prefix should be followed by the system and maybe subsystem,
* e.g. `ui.desktop.foo`, `ui.desktop.linux.bar`, `ui.android.foo`, `ui.dc40.bar`, `ui.bot.simplebot.baz`.
@@ -5480,11 +5479,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*/
#define DC_MSG_WEBXDC 80
/**
* Message containing shared contacts represented as a vCard (virtual contact file)
* with email addresses and possibly other fields.
*/
#define DC_MSG_VCARD 90
/**
* @}
@@ -7333,19 +7327,6 @@ void dc_event_unref(dc_event_t* event);
/// Used in summaries.
#define DC_STR_REACTED_BY 177
/// "Establishing guaranteed end-to-end encryption, please wait…"
///
/// Used as info message.
#define DC_STR_SECUREJOIN_WAIT 190
/// "Could not yet establish guaranteed end-to-end encryption, but you may already send a message."
///
/// Used as info message.
#define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191
/// "Contact"
#define DC_STR_CONTACT 200
/**
* @}
*/

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.138.5"
version = "1.137.4"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
@@ -15,7 +15,6 @@ required-features = ["webserver"]
[dependencies]
anyhow = "1"
deltachat = { path = ".." }
deltachat-contact-tools = { path = "../deltachat-contact-tools" }
num-traits = "0.2"
schemars = "0.8.19"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,6 +1,5 @@
use std::collections::BTreeMap;
use std::path::Path;
use std::str;
use std::sync::Arc;
use std::time::Duration;
use std::{collections::HashMap, str::FromStr};
@@ -43,7 +42,7 @@ pub mod types;
use num_traits::FromPrimitive;
use types::account::Account;
use types::chat::FullChat;
use types::contact::{ContactObject, VcardContact};
use types::contact::ContactObject;
use types::events::Event;
use types::http::HttpResponse;
use types::message::{MessageData, MessageObject, MessageReadReceipt};
@@ -1427,23 +1426,6 @@ impl CommandApi {
Ok(contact_id.map(|id| id.to_u32()))
}
/// Parses a vCard file located at the given path. Returns contacts in their original order.
async fn parse_vcard(&self, path: String) -> Result<Vec<VcardContact>> {
let vcard = tokio::fs::read(Path::new(&path)).await?;
let vcard = str::from_utf8(&vcard)?;
Ok(deltachat_contact_tools::parse_vcard(vcard)
.into_iter()
.map(|c| c.into())
.collect())
}
/// Returns a vCard containing contacts with the given ids.
async fn make_vcard(&self, account_id: u32, contacts: Vec<u32>) -> Result<String> {
let ctx = self.get_context(account_id).await?;
let contacts: Vec<_> = contacts.iter().map(|&c| ContactId::new(c)).collect();
deltachat::contact::make_vcard(&ctx, &contacts).await
}
// ---------------------------------------------
// chat
// ---------------------------------------------

View File

@@ -1,5 +1,4 @@
use anyhow::Result;
use deltachat::color;
use deltachat::context::Context;
use serde::Serialize;
use typescript_type_def::TypeDef;
@@ -88,35 +87,3 @@ impl ContactObject {
})
}
}
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct VcardContact {
/// Email address.
addr: String,
/// The contact's name, or the email address if no name was given.
display_name: String,
/// Public PGP key in Base64.
key: Option<String>,
/// Profile image in Base64.
profile_image: Option<String>,
/// Contact color as hex string.
color: String,
/// Last update timestamp.
timestamp: Option<i64>,
}
impl From<deltachat_contact_tools::VcardContact> for VcardContact {
fn from(vc: deltachat_contact_tools::VcardContact) -> Self {
let display_name = vc.display_name().to_string();
let color = color::str_to_color(&vc.addr.to_lowercase());
Self {
addr: vc.addr,
display_name,
key: vc.key,
profile_image: vc.profile_image,
color: color_int_to_hex_string(color),
timestamp: vc.timestamp.ok(),
}
}
}

View File

@@ -274,11 +274,6 @@ pub enum MessageViewtype {
/// Message is an webxdc instance.
Webxdc,
/// Message containing shared contacts represented as a vCard (virtual contact file)
/// with email addresses and possibly other fields.
/// Use `parse_vcard()` to retrieve them.
Vcard,
}
impl From<Viewtype> for MessageViewtype {
@@ -295,7 +290,6 @@ impl From<Viewtype> for MessageViewtype {
Viewtype::File => MessageViewtype::File,
Viewtype::VideochatInvitation => MessageViewtype::VideochatInvitation,
Viewtype::Webxdc => MessageViewtype::Webxdc,
Viewtype::Vcard => MessageViewtype::Vcard,
}
}
}
@@ -314,7 +308,6 @@ impl From<MessageViewtype> for Viewtype {
MessageViewtype::File => Viewtype::File,
MessageViewtype::VideochatInvitation => Viewtype::VideochatInvitation,
MessageViewtype::Webxdc => Viewtype::Webxdc,
MessageViewtype::Vcard => Viewtype::Vcard,
}
}
}
@@ -353,14 +346,6 @@ pub enum SystemMessageType {
LocationOnly,
InvalidUnencryptedMail,
/// 1:1 chats info message telling that SecureJoin has started and the user should wait for it
/// to complete.
SecurejoinWait,
/// 1:1 chats info message telling that SecureJoin is still running, but the user may already
/// send messages.
SecurejoinWaitTimeout,
/// Chat ephemeral message timer is changed.
EphemeralTimerChanged,
@@ -401,8 +386,6 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
SystemMessage::WebxdcStatusUpdate => SystemMessageType::WebxdcStatusUpdate,
SystemMessage::WebxdcInfoMessage => SystemMessageType::WebxdcInfoMessage,
SystemMessage::InvalidUnencryptedMail => SystemMessageType::InvalidUnencryptedMail,
SystemMessage::SecurejoinWait => SystemMessageType::SecurejoinWait,
SystemMessage::SecurejoinWaitTimeout => SystemMessageType::SecurejoinWaitTimeout,
}
}
}
@@ -652,7 +635,7 @@ impl MessageInfo {
#[derive(
Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema,
)]
#[serde(rename_all = "camelCase", tag = "kind")]
#[serde(rename_all = "camelCase", tag = "variant")]
pub enum EphemeralTimer {
/// Timer is disabled.
Disabled,

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.138.5"
version = "1.137.4"
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.138.5"
version = "1.137.4"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",

View File

@@ -61,7 +61,6 @@ class EventType(str, Enum):
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
CHATLIST_CHANGED = "ChatlistChanged"
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"
CONFIG_SYNCED = "ConfigSynced"
class ChatId(IntEnum):

View File

@@ -508,8 +508,8 @@ def test_reactions_for_a_reordering_move(acfactory):
"""
(ac1,) = acfactory.get_online_accounts(1)
ac2 = acfactory.new_preconfigured_account()
ac2.configure()
ac2.set_config("mvbox_move", "1")
ac2.configure()
ac2.bring_online()
chat1 = acfactory.get_accepted_chat(ac1, ac2)
ac2.stop_io()

View File

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

View File

@@ -1,19 +1,16 @@
{
"license": "MPL-2.0",
"main": "index.js",
"name": "@deltachat/stdio-rpc-server",
"optionalDependencies": {},
"peerDependencies": {
"@deltachat/jsonrpc-client": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/deltachat/deltachat-core-rust.git"
},
"scripts": {
"prepack": "node scripts/update_optional_dependencies_and_version.js"
},
"type": "module",
"types": "index.d.ts",
"version": "1.138.5"
}
"type": "module",
"name": "@deltachat/stdio-rpc-server",
"version": "1.137.4",
"license": "MPL-2.0",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"prepack": "node scripts/update_optional_dependencies_and_version.js"
},
"optionalDependencies": {
},
"peerDependencies": {
"@deltachat/jsonrpc-client": "*"
}
}

View File

@@ -13,21 +13,14 @@ def write_package_json(platform_path, rust_target, my_binary_name):
tomlfile = open("../../Cargo.toml", 'rb')
version = tomllib.load(tomlfile)['package']['version']
package_json = {
"name": "@deltachat/stdio-rpc-server-"
+ convert_os_to_npm_os(os)
+ "-"
+ convert_cpu_arch_to_npm_cpu_arch(cpu_arch),
package_json = dict({
"name": "@deltachat/stdio-rpc-server-" + convert_os_to_npm_os(os) + "-" + convert_cpu_arch_to_npm_cpu_arch(cpu_arch),
"version": version,
"os": [convert_os_to_npm_os(os)],
"cpu": [convert_cpu_arch_to_npm_cpu_arch(cpu_arch)],
"main": my_binary_name,
"license": "MPL-2.0",
"repository": {
"type": "git",
"url": "https://github.com/deltachat/deltachat-core-rust.git",
},
}
"license": "MPL-2.0"
})
file = open(platform_path + "/package.json", 'w')
file.write(json.dumps(package_json, indent=4))

View File

@@ -110,7 +110,6 @@ module.exports = {
DC_MSG_IMAGE: 20,
DC_MSG_STICKER: 23,
DC_MSG_TEXT: 10,
DC_MSG_VCARD: 90,
DC_MSG_VIDEO: 50,
DC_MSG_VIDEOCHAT_INVITATION: 70,
DC_MSG_VOICE: 41,
@@ -174,7 +173,6 @@ module.exports = {
DC_STR_CONFIGURATION_FAILED: 84,
DC_STR_CONNECTED: 107,
DC_STR_CONNTECTING: 108,
DC_STR_CONTACT: 200,
DC_STR_CONTACT_NOT_VERIFIED: 36,
DC_STR_CONTACT_SETUP_CHANGED: 37,
DC_STR_CONTACT_VERIFIED: 35,
@@ -268,8 +266,6 @@ module.exports = {
DC_STR_REMOVE_MEMBER_BY_YOU: 130,
DC_STR_REPLY_NOUN: 90,
DC_STR_SAVED_MESSAGES: 69,
DC_STR_SECUREJOIN_WAIT: 190,
DC_STR_SECUREJOIN_WAIT_TIMEOUT: 191,
DC_STR_SECURE_JOIN_GROUP_QR_DESC: 120,
DC_STR_SECURE_JOIN_REPLIES: 118,
DC_STR_SECURE_JOIN_STARTED: 117,

View File

@@ -110,7 +110,6 @@ export enum C {
DC_MSG_IMAGE = 20,
DC_MSG_STICKER = 23,
DC_MSG_TEXT = 10,
DC_MSG_VCARD = 90,
DC_MSG_VIDEO = 50,
DC_MSG_VIDEOCHAT_INVITATION = 70,
DC_MSG_VOICE = 41,
@@ -174,7 +173,6 @@ export enum C {
DC_STR_CONFIGURATION_FAILED = 84,
DC_STR_CONNECTED = 107,
DC_STR_CONNTECTING = 108,
DC_STR_CONTACT = 200,
DC_STR_CONTACT_NOT_VERIFIED = 36,
DC_STR_CONTACT_SETUP_CHANGED = 37,
DC_STR_CONTACT_VERIFIED = 35,
@@ -268,8 +266,6 @@ export enum C {
DC_STR_REMOVE_MEMBER_BY_YOU = 130,
DC_STR_REPLY_NOUN = 90,
DC_STR_SAVED_MESSAGES = 69,
DC_STR_SECUREJOIN_WAIT = 190,
DC_STR_SECUREJOIN_WAIT_TIMEOUT = 191,
DC_STR_SECURE_JOIN_GROUP_QR_DESC = 120,
DC_STR_SECURE_JOIN_REPLIES = 118,
DC_STR_SECURE_JOIN_STARTED = 117,

View File

@@ -55,5 +55,5 @@
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.138.5"
"version": "1.137.4"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "1.138.5"
version = "1.137.4"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"
requires-python = ">=3.7"

View File

@@ -275,7 +275,6 @@ class ACSetup:
def __init__(self, testprocess, init_time) -> None:
self._configured_events = Queue()
self._account2state: Dict[Account, str] = {}
self._account2config: Dict[Account, Dict[str, str]] = {}
self._imap_cleaned: Set[str] = set()
self.testprocess = testprocess
self.init_time = init_time
@@ -337,8 +336,6 @@ class ACSetup:
if not success:
pytest.fail(f"configuring online account {acc} failed: {comment}")
self._account2state[acc] = self.CONFIGURED
if acc in self._account2config:
acc.update_config(self._account2config[acc])
return acc
def _onconfigure_start_io(self, acc):
@@ -526,7 +523,6 @@ class ACFactory:
configdict.setdefault("sentbox_watch", False)
configdict.setdefault("sync_msgs", False)
ac.update_config(configdict)
self._acsetup._account2config[ac] = configdict
self._preconfigure_key(ac, configdict["addr"])
return ac

View File

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

View File

@@ -12,7 +12,6 @@ use deltachat_contact_tools::{strip_rtlo_characters, ContactAddress};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use tokio::task;
use crate::aheader::EncryptPreference;
use crate::blob::BlobObject;
@@ -39,7 +38,6 @@ use crate::mimeparser::SystemMessage;
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::receive_imf::ReceivedMsg;
use crate::securejoin::BobState;
use crate::smtp::send_msg_to_smtp;
use crate::sql;
use crate::stock_str;
@@ -128,10 +126,6 @@ pub(crate) enum CantSendReason {
/// Not a member of the chat.
NotAMember,
/// Temporary state for 1:1 chats while SecureJoin is in progress, after a timeout sending
/// messages (incl. unencrypted if we don't yet know the contact's pubkey) is allowed.
SecurejoinWait,
}
impl fmt::Display for CantSendReason {
@@ -151,7 +145,6 @@ impl fmt::Display for CantSendReason {
write!(f, "mailing list does not have a know post address")
}
Self::NotAMember => write!(f, "not a member of the chat"),
Self::SecurejoinWait => write!(f, "awaiting SecureJoin for 1:1 chat"),
}
}
}
@@ -617,10 +610,7 @@ impl ChatId {
let sort_to_bottom = true;
let ts = self
.calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, false)
.await?
// Always sort protection messages below `SystemMessage::SecurejoinWait{,Timeout}` ones
// in case of race conditions.
.saturating_add(1);
.await?;
self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
.await
}
@@ -1417,18 +1407,6 @@ impl ChatId {
Ok(sort_timestamp)
}
/// Spawns a task checking after a timeout whether the SecureJoin has finished for the 1:1 chat
/// and otherwise notifying the user accordingly.
pub(crate) fn spawn_securejoin_wait(self, context: &Context, timeout: u64) {
let context = context.clone();
task::spawn(async move {
tokio::time::sleep(Duration::from_secs(timeout)).await;
let chat = Chat::load_from_db(&context, self).await?;
chat.check_securejoin_wait(&context, 0).await?;
Result::<()>::Ok(())
});
}
}
impl std::fmt::Display for ChatId {
@@ -1608,12 +1586,6 @@ impl Chat {
Some(ReadOnlyMailingList)
} else if !self.is_self_in_chat(context).await? {
Some(NotAMember)
} else if self
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
.await?
> 0
{
Some(SecurejoinWait)
} else {
None
};
@@ -1627,69 +1599,6 @@ impl Chat {
Ok(self.why_cant_send(context).await?.is_none())
}
/// Returns the remaining timeout for the 1:1 chat in-progress SecureJoin.
///
/// If the timeout has expired, notifies the user that sending messages is possible. See also
/// [`CantSendReason::SecurejoinWait`].
pub(crate) async fn check_securejoin_wait(
&self,
context: &Context,
timeout: u64,
) -> Result<u64> {
if self.typ != Chattype::Single || self.protected != ProtectionStatus::Unprotected {
return Ok(0);
}
let (mut param0, mut param1) = (Params::new(), Params::new());
param0.set_cmd(SystemMessage::SecurejoinWait);
param1.set_cmd(SystemMessage::SecurejoinWaitTimeout);
let (param0, param1) = (param0.to_string(), param1.to_string());
let Some((param, ts_sort, ts_start)) = context
.sql
.query_row_optional(
"SELECT param, timestamp, timestamp_sent FROM msgs WHERE id=\
(SELECT MAX(id) FROM msgs WHERE chat_id=? AND param IN (?, ?))",
(self.id, &param0, &param1),
|row| {
let param: String = row.get(0)?;
let ts_sort: i64 = row.get(1)?;
let ts_start: i64 = row.get(2)?;
Ok((param, ts_sort, ts_start))
},
)
.await?
else {
return Ok(0);
};
if param == param1 {
return Ok(0);
}
let now = time();
// Don't await SecureJoin if the clock was set back.
if ts_start <= now {
let timeout = ts_start
.saturating_add(timeout.try_into()?)
.saturating_sub(now);
if timeout > 0 {
return Ok(timeout as u64);
}
}
add_info_msg_with_cmd(
context,
self.id,
&stock_str::securejoin_wait_timeout(context).await,
SystemMessage::SecurejoinWaitTimeout,
// Use the sort timestamp of the "please wait" message, this way the added message is
// never sorted below the protection message if the SecureJoin finishes in parallel.
ts_sort,
Some(now),
None,
None,
)
.await?;
context.emit_event(EventType::ChatModified(self.id));
Ok(0)
}
/// Checks if the user is part of a chat
/// and has basically the permissions to edit the chat therefore.
/// The function does not check if the chat type allows editing of concrete elements.
@@ -2425,26 +2334,6 @@ pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
Ok(())
}
/// Checks if there is a 1:1 chat in-progress SecureJoin for Bob and, if necessary, schedules a task
/// unblocking the chat and notifying the user accordingly.
pub(crate) async fn resume_securejoin_wait(context: &Context) -> Result<()> {
let Some(bobstate) = BobState::from_db(&context.sql).await? else {
return Ok(());
};
if !bobstate.in_progress() {
return Ok(());
}
let chat_id = bobstate.alice_chat();
let chat = Chat::load_from_db(context, chat_id).await?;
let timeout = chat
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
.await?;
if timeout > 0 {
chat_id.spawn_securejoin_wait(context, timeout);
}
Ok(())
}
/// Handle a [`ChatId`] and its [`Blocked`] status at once.
///
/// This struct is an optimisation to read a [`ChatId`] and its [`Blocked`] status at once
@@ -2698,9 +2587,7 @@ async fn prepare_msg_common(
if let Some(reason) = chat.why_cant_send(context).await? {
if matches!(
reason,
CantSendReason::ProtectionBroken
| CantSendReason::ContactRequest
| CantSendReason::SecurejoinWait
CantSendReason::ProtectionBroken | CantSendReason::ContactRequest
) && msg.param.get_cmd() == SystemMessage::SecurejoinMessage
{
// Send out the message, the securejoin message is supposed to repair the verification.

View File

@@ -31,11 +31,10 @@ fn rgb_to_u32((r, g, b): (f64, f64, f64)) -> u32 {
///
/// Saturation is set to maximum (100.0) to make colors distinguishable, and lightness is set to
/// half (50.0) to make colors suitable both for light and dark theme.
pub fn str_to_color(s: &str) -> u32 {
pub(crate) fn str_to_color(s: &str) -> u32 {
rgb_to_u32(hsluv_to_rgb((str_to_angle(s), 100.0, 50.0)))
}
/// Returns color as a "#RRGGBB" `String` where R, G, B are hex digits.
pub fn color_int_to_hex_string(color: u32) -> String {
format!("{color:#08x}").replace("0x", "#")
}

View File

@@ -254,9 +254,6 @@ pub enum Config {
/// True if account is configured.
Configured,
/// True if account is a chatmail account.
IsChatmail,
/// All secondary self addresses separated by spaces
/// (`addr1@example.org addr2@example.org addr3@example.org`)
SecondaryAddrs,

View File

@@ -112,11 +112,6 @@ impl Context {
let mut param = LoginParam::load_candidate_params(self).await?;
let old_addr = self.get_config(Config::ConfiguredAddr).await?;
// Reset our knowledge about whether the server is a chatmail server.
// We will update it when we connect to IMAP.
self.set_config_internal(Config::IsChatmail, None).await?;
let success = configure(self, &mut param).await;
self.set_config_internal(Config::NotifyAboutWrongPw, None)
.await?;
@@ -458,14 +453,6 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
progress!(ctx, 900);
if imap_session.is_chatmail() {
ctx.set_config(Config::SentboxWatch, None).await?;
ctx.set_config(Config::MvboxMove, Some("0")).await?;
ctx.set_config(Config::OnlyFetchMvbox, None).await?;
ctx.set_config(Config::ShowEmails, None).await?;
ctx.set_config(Config::E2eeEnabled, Some("1")).await?;
}
let create_mvbox = ctx.should_watch_mvbox().await?;
imap.configure_folders(ctx, &mut imap_session, create_mvbox)

View File

@@ -223,11 +223,6 @@ pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: u64 = 12 * 60 * 60;
/// in the group membership consistency algo to reject outdated membership changes.
pub(crate) const TIMESTAMP_SENT_TOLERANCE: i64 = 60;
/// How long a 1:1 chat can't be used for sending while the SecureJoin is in progress. This should
/// be 10-20 seconds so that we are reasonably sure that the app remains active and receiving also
/// on mobile devices. See also [`crate::chat::CantSendReason::SecurejoinWait`].
pub(crate) const SECUREJOIN_WAIT_TIMEOUT: u64 = 15;
#[cfg(test)]
mod tests {
use num_traits::FromPrimitive;

View File

@@ -8,11 +8,10 @@ use std::time::UNIX_EPOCH;
use anyhow::{bail, ensure, Context as _, Result};
use async_channel::{self as channel, Receiver, Sender};
use base64::Engine as _;
pub use deltachat_contact_tools::may_be_valid_addr;
use deltachat_contact_tools::{
self as contact_tools, addr_cmp, addr_normalize, normalize_name, sanitize_name_and_addr,
strip_rtlo_characters, ContactAddress, VcardContact,
addr_cmp, addr_normalize, normalize_name, sanitize_name_and_addr, strip_rtlo_characters,
ContactAddress,
};
use deltachat_derive::{FromSql, ToSql};
use rusqlite::OptionalExtension;
@@ -160,35 +159,6 @@ impl rusqlite::types::FromSql for ContactId {
}
}
/// Returns a vCard containing contacts with the given ids.
pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
let now = time();
let mut vcard_contacts = Vec::with_capacity(contacts.len());
for id in contacts {
let c = Contact::get_by_id(context, *id).await?;
let key = Peerstate::from_addr(context, &c.addr)
.await?
.and_then(|peerstate| peerstate.peek_key(false).map(|k| k.to_base64()));
let profile_image = match c.get_profile_image(context).await? {
None => None,
Some(path) => tokio::fs::read(path)
.await
.log_err(context)
.ok()
.map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
};
vcard_contacts.push(VcardContact {
addr: c.addr,
authname: c.authname,
key,
profile_image,
// Use the current time to not reveal our or contact's online time.
timestamp: Ok(now),
});
}
Ok(contact_tools::make_vcard(&vcard_contacts))
}
/// An object representing a single contact in memory.
///
/// The contact object is not updated.
@@ -2847,53 +2817,4 @@ Until the false-positive is fixed:
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_make_vcard() -> Result<()> {
let alice = &TestContext::new_alice().await;
let bob = &TestContext::new_bob().await;
bob.set_config(Config::Displayname, Some("Bob")).await?;
let avatar_path = bob.dir.path().join("avatar.png");
let avatar_bytes = include_bytes!("../test-data/image/avatar64x64.png");
let avatar_base64 = base64::engine::general_purpose::STANDARD.encode(avatar_bytes);
tokio::fs::write(&avatar_path, avatar_bytes).await?;
bob.set_config(Config::Selfavatar, Some(avatar_path.to_str().unwrap()))
.await?;
let bob_addr = bob.get_config(Config::Addr).await?.unwrap();
let chat = bob.create_chat(alice).await;
let sent_msg = bob.send_text(chat.id, "moin").await;
alice.recv_msg(&sent_msg).await;
let bob_id = Contact::create(alice, "Some Bob", &bob_addr).await?;
let key_base64 = Peerstate::from_addr(alice, &bob_addr)
.await?
.unwrap()
.peek_key(false)
.unwrap()
.to_base64();
let fiona_id = Contact::create(alice, "Fiona", "fiona@example.net").await?;
assert_eq!(make_vcard(alice, &[]).await?, "".to_string());
let t0 = time();
let vcard = make_vcard(alice, &[bob_id, fiona_id]).await?;
let t1 = time();
// Just test that it's parsed as expected, `deltachat_contact_tools` crate has tests on the
// exact format.
let contacts = contact_tools::parse_vcard(&vcard);
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0].addr, bob_addr);
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(contacts[0].key, Some(key_base64));
assert_eq!(contacts[0].profile_image, Some(avatar_base64));
let timestamp = *contacts[0].timestamp.as_ref().unwrap();
assert!(t0 <= timestamp && timestamp <= t1);
assert_eq!(contacts[1].addr, "fiona@example.net".to_string());
assert_eq!(contacts[1].authname, "".to_string());
assert_eq!(contacts[1].key, None);
assert_eq!(contacts[1].profile_image, None);
let timestamp = *contacts[1].timestamp.as_ref().unwrap();
assert!(t0 <= timestamp && timestamp <= t1);
Ok(())
}
}

View File

@@ -461,10 +461,18 @@ impl Context {
return;
}
if self.is_chatmail().await.unwrap_or_default() {
let mut lock = self.ratelimit.write().await;
// Allow at least 1 message every second + a burst of 3.
*lock = Ratelimit::new(Duration::new(3, 0), 3.0);
{
if self
.get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default()
.filter(|s| s.ends_with(".testrun.org"))
.is_some()
{
let mut lock = self.ratelimit.write().await;
// Allow at least 1 message every second + a burst of 3.
*lock = Ratelimit::new(Duration::new(3, 0), 3.0);
}
}
self.scheduler.start(self.clone()).await;
}
@@ -485,11 +493,6 @@ impl Context {
self.scheduler.maybe_network().await;
}
/// 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
}
/// Does a background fetch
/// pauses the scheduler and does one imap fetch, then unpauses and returns
pub async fn background_fetch(&self) -> Result<()> {
@@ -796,8 +799,6 @@ impl Context {
res.insert("imap_server_id", format!("{server_id:?}"));
}
res.insert("is_chatmail", self.is_chatmail().await?.to_string());
if let Some(metadata) = &*self.metadata.read().await {
if let Some(comment) = &metadata.comment {
res.insert("imap_server_comment", format!("{comment:?}"));

View File

@@ -32,14 +32,6 @@ pub(crate) struct Capabilities {
/// This is supported by <https://github.com/deltachat/chatmail>
pub can_push: bool,
/// True if the server has an XCHATMAIL capability
/// indicating that it is a <https://github.com/deltachat/chatmail> server.
///
/// This can be used to hide some advanced settings in the UI
/// that are only interesting for normal email accounts,
/// e.g. the ability to move messages to Delta Chat folder.
pub is_chatmail: bool,
/// Server ID if the server supports ID capability.
pub server_id: Option<HashMap<String, String>>,
}

View File

@@ -61,7 +61,6 @@ async fn determine_capabilities(
can_condstore: caps.has_str("CONDSTORE"),
can_metadata: caps.has_str("METADATA"),
can_push: caps.has_str("XDELTAPUSH"),
is_chatmail: caps.has_str("XCHATMAIL"),
server_id,
};
Ok(capabilities)

View File

@@ -94,11 +94,6 @@ impl Session {
self.capabilities.can_push
}
// Returns true if IMAP server has `XCHATMAIL` capability.
pub fn is_chatmail(&self) -> bool {
self.capabilities.is_chatmail
}
/// Returns the names of all folders on the IMAP server.
pub async fn list_folders(&mut self) -> Result<Vec<async_imap::types::Name>> {
let list = self.list(Some(""), Some("*")).await?.try_collect().await?;

View File

@@ -94,7 +94,7 @@ pub mod webxdc;
#[macro_use]
mod dehtml;
mod authres;
pub mod color;
mod color;
pub mod html;
pub mod net;
pub mod plaintext;

View File

@@ -1416,8 +1416,8 @@ pub(crate) fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)>
"tif" => (Viewtype::File, "image/tiff"),
"ttf" => (Viewtype::File, "font/ttf"),
"txt" => (Viewtype::File, "text/plain"),
"vcard" => (Viewtype::Vcard, "text/vcard"),
"vcf" => (Viewtype::Vcard, "text/vcard"),
"vcard" => (Viewtype::File, "text/vcard"),
"vcf" => (Viewtype::File, "text/vcard"),
"wav" => (Viewtype::File, "audio/wav"),
"weba" => (Viewtype::File, "audio/webm"),
"webm" => (Viewtype::Video, "video/webm"),
@@ -1938,8 +1938,7 @@ pub enum Viewtype {
Text = 10,
/// Image message.
/// If the image is a GIF and has the appropriate extension, the viewtype is auto-changed to
/// `Gif` when sending the message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
Image = 20,
@@ -1983,11 +1982,6 @@ pub enum Viewtype {
/// Message is an webxdc instance.
Webxdc = 80,
/// Message containing shared contacts represented as a vCard (virtual contact file)
/// with email addresses and possibly other fields.
/// Use `parse_vcard()` to retrieve them.
Vcard = 90,
}
impl Viewtype {
@@ -2005,7 +1999,6 @@ impl Viewtype {
Viewtype::File => true,
Viewtype::VideochatInvitation => false,
Viewtype::Webxdc => true,
Viewtype::Vcard => true,
}
}
}
@@ -2519,7 +2512,6 @@ mod tests {
Viewtype::from_i32(70).unwrap()
);
assert_eq!(Viewtype::Webxdc, Viewtype::from_i32(80).unwrap());
assert_eq!(Viewtype::Vcard, Viewtype::from_i32(90).unwrap());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -36,7 +36,6 @@ use crate::simplify::{simplify, SimplifiedText};
use crate::sync::SyncItems;
use crate::tools::{
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time, truncate_by_lines,
validate_id,
};
use crate::{chatlist_events, location, stock_str, tools};
@@ -180,14 +179,6 @@ pub enum SystemMessage {
/// which is sent by chatmail servers.
InvalidUnencryptedMail = 13,
/// 1:1 chats info message telling that SecureJoin has started and the user should wait for it
/// to complete.
SecurejoinWait = 14,
/// 1:1 chats info message telling that SecureJoin is still running, but the user may already
/// send messages.
SecurejoinWaitTimeout = 15,
/// Self-sent-message that contains only json used for multi-device-sync;
/// if possible, we attach that to other messages as for locations.
MultiDeviceSync = 20,
@@ -556,25 +547,19 @@ impl MimeMessage {
/// Parses avatar action headers.
async fn parse_avatar_headers(&mut self, context: &Context) {
if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) {
self.group_avatar = self
.avatar_action_from_header(context, header_value.to_string())
.await;
if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar).cloned() {
self.group_avatar = self.avatar_action_from_header(context, header_value).await;
}
if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) {
self.user_avatar = self
.avatar_action_from_header(context, header_value.to_string())
.await;
if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar).cloned() {
self.user_avatar = self.avatar_action_from_header(context, header_value).await;
}
}
fn parse_videochat_headers(&mut self) {
if let Some(value) = self.get_header(HeaderDef::ChatContent) {
if let Some(value) = self.get_header(HeaderDef::ChatContent).cloned() {
if value == "videochat-invitation" {
let instance = self
.get_header(HeaderDef::ChatWebrtcRoom)
.map(|s| s.to_string());
let instance = self.get_header(HeaderDef::ChatWebrtcRoom).cloned();
if let Some(part) = self.parts.first_mut() {
part.typ = Viewtype::VideochatInvitation;
part.param
@@ -600,7 +585,6 @@ impl MimeMessage {
| Viewtype::Audio
| Viewtype::Voice
| Viewtype::Video
| Viewtype::Vcard
| Viewtype::File
| Viewtype::Webxdc => true,
Viewtype::Unknown | Viewtype::Text | Viewtype::VideochatInvitation => false,
@@ -822,16 +806,8 @@ impl MimeMessage {
.map(|s| s.to_string())
}
pub fn get_header(&self, headerdef: HeaderDef) -> Option<&str> {
self.headers
.get(headerdef.get_headername())
.map(|s| s.as_str())
}
/// Returns `Chat-Group-ID` header value if it is a valid group ID.
pub fn get_chat_group_id(&self) -> Option<&str> {
self.get_header(HeaderDef::ChatGroupId)
.filter(|s| validate_id(s))
pub fn get_header(&self, headerdef: HeaderDef) -> Option<&String> {
self.headers.get(headerdef.get_headername())
}
async fn parse_mime_recursive<'a>(
@@ -1934,11 +1910,16 @@ fn get_mime_type(
let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
let viewtype = match mimetype.type_() {
mime::TEXT => match mimetype.subtype() {
mime::VCARD if is_valid_deltachat_vcard(mail) => Viewtype::Vcard,
mime::PLAIN | mime::HTML if !is_attachment_disposition(mail) => Viewtype::Text,
_ => Viewtype::File,
},
mime::TEXT => {
if !is_attachment_disposition(mail) {
match mimetype.subtype() {
mime::PLAIN | mime::HTML => Viewtype::Text,
_ => Viewtype::File,
}
} else {
Viewtype::File
}
}
mime::IMAGE => match mimetype.subtype() {
mime::GIF => Viewtype::Gif,
mime::SVG => Viewtype::File,
@@ -1986,17 +1967,6 @@ fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
.any(|(key, _value)| key.starts_with("filename"))
}
fn is_valid_deltachat_vcard(mail: &mailparse::ParsedMail) -> bool {
let Ok(body) = &mail.get_body() else {
return false;
};
let contacts = deltachat_contact_tools::parse_vcard(body);
if let [c] = &contacts[..] {
return deltachat_contact_tools::may_be_valid_addr(&c.addr);
}
false
}
/// Tries to get attachment filename.
///
/// If filename is explicitly specified in Content-Disposition, it is

View File

@@ -37,7 +37,7 @@ use crate::simplify;
use crate::sql;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{self, buf_compress, extract_grpid_from_rfc724_mid};
use crate::tools::{self, buf_compress, extract_grpid_from_rfc724_mid, validate_id};
use crate::{chatlist_events, location};
use crate::{contact, imap};
@@ -703,9 +703,7 @@ async fn add_parts(
better_msg = Some(stock_str::msg_location_enabled_by(context, from_id).await);
}
let parent = get_parent_message(context, mime_parser)
.await?
.filter(|p| Some(p.id) != replace_msg_id);
let parent = get_parent_message(context, mime_parser).await?;
let is_dc_message = if mime_parser.has_chat_version() {
MessengerMessage::Yes
@@ -771,18 +769,6 @@ async fn add_parts(
info!(context, "Message is an MDN (TRASH).",);
}
// Try to assign to a chat based on Chat-Group-ID.
if chat_id.is_none() {
if let Some(grpid) = mime_parser.get_chat_group_id() {
if let Some((id, _protected, blocked)) =
chat::get_chat_id_by_grpid(context, grpid).await?
{
chat_id = Some(id);
chat_id_blocked = blocked;
}
}
}
if chat_id.is_none() {
// try to assign to a chat based on In-Reply-To/References:
@@ -1049,18 +1035,6 @@ async fn add_parts(
chat_id = Some(DC_CHAT_ID_TRASH);
}
// Try to assign to a chat based on Chat-Group-ID.
if chat_id.is_none() {
if let Some(grpid) = mime_parser.get_chat_group_id() {
if let Some((id, _protected, blocked)) =
chat::get_chat_id_by_grpid(context, grpid).await?
{
chat_id = Some(id);
chat_id_blocked = blocked;
}
}
}
if chat_id.is_none() {
// try to assign to a chat based on In-Reply-To/References:
@@ -1373,9 +1347,11 @@ async fn add_parts(
let mime_in_reply_to = mime_parser
.get_header(HeaderDef::InReplyTo)
.cloned()
.unwrap_or_default();
let mime_references = mime_parser
.get_header(HeaderDef::References)
.cloned()
.unwrap_or_default();
// fine, so far. now, split the message into simple parts usable as "short messages"
@@ -1435,7 +1411,7 @@ async fn add_parts(
let reaction_str = simplify::remove_footers(part.msg.as_str());
set_msg_reaction(
context,
mime_in_reply_to,
&mime_in_reply_to,
orig_chat_id.unwrap_or_default(),
from_id,
sort_timestamp,
@@ -1778,11 +1754,6 @@ async fn is_probably_private_reply(
return Ok(false);
}
// Message cannot be a private reply if it has an explicit Chat-Group-ID header.
if mime_parser.get_chat_group_id().is_some() {
return Ok(false);
}
if !mime_parser.has_chat_version() {
let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
@@ -2460,8 +2431,11 @@ async fn apply_mailinglist_changes(
}
fn try_getting_grpid(mime_parser: &MimeMessage) -> Option<String> {
if let Some(optional_field) = mime_parser.get_chat_group_id() {
return Some(optional_field.to_string());
if let Some(optional_field) = mime_parser
.get_header(HeaderDef::ChatGroupId)
.filter(|s| validate_id(s))
{
return Some(optional_field.clone());
}
// Useful for undecipherable messages sent to known group.

View File

@@ -2693,103 +2693,6 @@ Second thread."#;
Ok(())
}
/// Test that `Chat-Group-ID` is preferred over `In-Reply-To` and `References`.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_chat_assignment_chat_group_id_preference() -> Result<()> {
let t = &TestContext::new_alice().await;
receive_imf(
t,
br#"Subject: Hello
Chat-Group-ID: eJ_llQIXf0K
Chat-Group-Name: Group name
Chat-Version: 1.0
Message-ID: <first@localhost>
References: <first@localhost>
Date: Fri, 28 May 2021 10:15:05 +0000
From: Alice <alice@example.org>
To: Bob <bob@example.com>, <claire@example.org>
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Content-Transfer-Encoding: quoted-printable
Hello, I've just created a group for us."#,
false,
)
.await?;
let group_msg = t.get_last_msg().await;
receive_imf(
t,
br#"Subject: Hello
Chat-Version: 1.0
Message-ID: <second@localhost>
References: <second@localhost>
Date: Fri, 28 May 2021 10:15:05 +0000
From: Bob <bob@example.com>
To: Alice <alice@example.org>
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Content-Transfer-Encoding: quoted-printable
Hello from Bob in 1:1 chat."#,
false,
)
.await?;
// References and In-Reply-To point to a message
// already assigned to 1:1 chat, but Chat-Group-ID is
// a stronger signal to assign message to a group.
receive_imf(
t,
br#"Subject: Hello
Chat-Group-ID: eJ_llQIXf0K
Chat-Group-Name: Group name
Chat-Version: 1.0
Message-ID: <third@localhost>
In-Reply-To: <second@localhost>
References: <second@localhost>
Date: Fri, 28 May 2021 10:15:05 +0000
From: Bob <bob@example.com>
To: Alice <alice@example.org>, <claire@example.org>
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Content-Transfer-Encoding: quoted-printable
Hello from Bob in a group."#,
false,
)
.await?;
let msg = t.get_last_msg().await;
assert_eq!(msg.text, "Hello from Bob in a group.");
assert_eq!(msg.chat_id, group_msg.chat_id);
// Test outgoing message as well.
receive_imf(
t,
br#"Subject: Hello
Chat-Group-ID: eJ_llQIXf0K
Chat-Group-Name: Group name
Chat-Version: 1.0
Message-ID: <fourth@localhost>
In-Reply-To: <second@localhost>
References: <second@localhost>
Date: Fri, 28 May 2021 10:15:05 +0000
From: Alice <alice@example.org>
To: Bob <bob@example.com>, <claire@example.org>
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Content-Transfer-Encoding: quoted-printable
Hello from Alice in a group."#,
false,
)
.await?;
let msg_outgoing = t.get_last_msg().await;
assert_eq!(msg_outgoing.text, "Hello from Alice in a group.");
assert_eq!(msg_outgoing.chat_id, group_msg.chat_id);
Ok(())
}
/// Test that read receipts don't create chats.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_read_receipts_dont_create_chats() -> Result<()> {
@@ -4552,58 +4455,3 @@ async fn test_list_from() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_receive_vcard() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
for vcard_contains_address in [true, false] {
let mut msg = Message::new(Viewtype::Vcard);
msg.set_file_from_bytes(
&alice,
"claire.vcf",
format!(
"BEGIN:VCARD\n\
VERSION:4.0\n\
FN:Claire\n\
{}\
END:VCARD",
if vcard_contains_address {
"EMAIL;TYPE=work:claire@example.org\n"
} else {
""
}
)
.as_bytes(),
None,
)
.await
.unwrap();
let alice_bob_chat = alice.create_chat(&bob).await;
let sent = alice.send_msg(alice_bob_chat.id, &mut msg).await;
let rcvd = bob.recv_msg(&sent).await;
if vcard_contains_address {
assert_eq!(rcvd.viewtype, Viewtype::Vcard);
} else {
// VCards without an email address are not "deltachat contacts",
// so they are shown as files
assert_eq!(rcvd.viewtype, Viewtype::File);
}
let vcard = tokio::fs::read(rcvd.get_file(&bob).unwrap()).await?;
let vcard = std::str::from_utf8(&vcard)?;
let parsed = deltachat_contact_tools::parse_vcard(vcard);
assert_eq!(parsed.len(), 1);
if vcard_contains_address {
assert_eq!(&parsed[0].addr, "claire@example.org");
} else {
assert_eq!(&parsed[0].addr, "");
}
}
Ok(())
}

View File

@@ -466,12 +466,6 @@ pub async fn convert_folder_meaning(
}
async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> {
ctx.set_config_internal(
Config::IsChatmail,
crate::config::from_bool(session.is_chatmail()),
)
.await?;
// Update quota no more than once a minute.
let quota_needs_update = {
let quota = ctx.quota.read().await;

View File

@@ -29,7 +29,7 @@ mod bob;
mod bobstate;
mod qrinvite;
pub(crate) use bobstate::BobState;
use bobstate::BobState;
use qrinvite::QrInvite;
use crate::token::Namespace;
@@ -65,8 +65,8 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
let sync_token = token::lookup(context, Namespace::InviteNumber, group)
.await?
.is_none();
let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, group).await?;
let auth = token::lookup_or_new(context, Namespace::Auth, group).await?;
let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, group).await;
let auth = token::lookup_or_new(context, Namespace::Auth, group).await;
let self_addr = context.get_primary_self_addr().await?;
let self_name = context
.get_config(Config::Displayname)
@@ -294,7 +294,7 @@ pub(crate) async fn handle_securejoin_handshake(
let join_vg = step.starts_with("vg-");
if !matches!(step, "vg-request" | "vc-request") {
if !matches!(step.as_str(), "vg-request" | "vc-request") {
let mut self_found = false;
let self_fingerprint = load_self_public_key(context).await?.fingerprint();
for (addr, key) in &mime_message.gossiped_keys {
@@ -311,7 +311,7 @@ pub(crate) async fn handle_securejoin_handshake(
}
}
match step {
match step.as_str() {
"vg-request" | "vc-request" => {
/*=======================================================
==== Alice - the inviter side ====
@@ -487,7 +487,7 @@ pub(crate) async fn handle_securejoin_handshake(
=======================================================*/
"vc-contact-confirm" => {
if let Some(mut bobstate) = BobState::from_db(&context.sql).await? {
if !bobstate.is_msg_expected(context, step) {
if !bobstate.is_msg_expected(context, step.as_str()) {
warn!(context, "Unexpected vc-contact-confirm.");
return Ok(HandshakeMessage::Ignore);
}
@@ -498,7 +498,9 @@ pub(crate) async fn handle_securejoin_handshake(
Ok(HandshakeMessage::Ignore)
}
"vg-member-added" => {
let Some(member_added) = mime_message.get_header(HeaderDef::ChatGroupMemberAdded)
let Some(member_added) = mime_message
.get_header(HeaderDef::ChatGroupMemberAdded)
.map(|s| s.as_str())
else {
warn!(
context,
@@ -514,7 +516,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Propagate);
}
if let Some(mut bobstate) = BobState::from_db(&context.sql).await? {
if !bobstate.is_msg_expected(context, step) {
if !bobstate.is_msg_expected(context, step.as_str()) {
warn!(context, "Unexpected vg-member-added.");
return Ok(HandshakeMessage::Propagate);
}
@@ -569,7 +571,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
info!(context, "Observing secure-join message {step:?}.");
if !matches!(
step,
step.as_str(),
"vg-request-with-auth" | "vc-request-with-auth" | "vg-member-added" | "vc-contact-confirm"
) {
return Ok(HandshakeMessage::Ignore);
@@ -640,21 +642,21 @@ pub(crate) async fn observe_securejoin_on_other_device(
ChatId::set_protection_for_contact(context, contact_id, mime_message.timestamp_sent).await?;
if step == "vg-member-added" {
if step.as_str() == "vg-member-added" {
inviter_progress(context, contact_id, 800);
}
if step == "vg-member-added" || step == "vc-contact-confirm" {
if step.as_str() == "vg-member-added" || step.as_str() == "vc-contact-confirm" {
inviter_progress(context, contact_id, 1000);
}
if step == "vg-request-with-auth" || step == "vc-request-with-auth" {
if step.as_str() == "vg-request-with-auth" || step.as_str() == "vc-request-with-auth" {
// This actually reflects what happens on the first device (which does the secure
// join) and causes a subsequent "vg-member-added" message to create an unblocked
// verified group.
ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await?;
}
if step == "vg-member-added" {
if step.as_str() == "vg-member-added" {
Ok(HandshakeMessage::Propagate)
} else {
Ok(HandshakeMessage::Ignore)
@@ -762,7 +764,7 @@ mod tests {
use crate::constants::Chattype;
use crate::imex::{imex, ImexMode};
use crate::receive_imf::receive_imf;
use crate::stock_str::{self, chat_protection_enabled};
use crate::stock_str::chat_protection_enabled;
use crate::test_utils::get_chat_msg;
use crate::test_utils::{TestContext, TestContextManager};
use crate::tools::SystemTime;
@@ -959,7 +961,7 @@ mod tests {
let expected_text = chat_protection_enabled(&alice).await;
assert_eq!(msg.get_text(), expected_text);
if case == SetupContactCase::CheckProtectionTimestamp {
assert_eq!(msg.timestamp_sort, vc_request_with_auth_ts_sent + 1);
assert_eq!(msg.timestamp_sort, vc_request_with_auth_ts_sent);
}
}
@@ -988,12 +990,10 @@ mod tests {
// Check Bob got the verified message in his 1:1 chat.
let chat = bob.create_chat(&alice).await;
let msg = get_chat_msg(&bob, chat.get_id(), 0, 2).await;
let msg = get_chat_msg(&bob, chat.get_id(), 0, 1).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob).await);
let msg = get_chat_msg(&bob, chat.get_id(), 1, 2).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), chat_protection_enabled(&bob).await);
let expected_text = chat_protection_enabled(&bob).await;
assert_eq!(msg.get_text(), expected_text);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -9,11 +9,11 @@ use super::bobstate::{BobHandshakeStage, BobState};
use super::qrinvite::QrInvite;
use super::HandshakeMessage;
use crate::chat::{is_contact_in_chat, ChatId, ProtectionStatus};
use crate::constants::{self, Blocked, Chattype};
use crate::constants::{Blocked, Chattype};
use crate::contact::Contact;
use crate::context::Context;
use crate::events::EventType;
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::mimeparser::MimeMessage;
use crate::sync::Sync::*;
use crate::tools::{create_smeared_timestamp, time};
use crate::{chat, stock_str};
@@ -69,29 +69,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
QrInvite::Contact { .. } => {
// For setup-contact the BobState already ensured the 1:1 chat exists because it
// uses it to send the handshake messages.
let chat_id = state.alice_chat();
// Calculate the sort timestamp before checking the chat protection status so that if we
// race with its change, we don't add our message below the protection message.
let sort_to_bottom = true;
let ts_sort = chat_id
.calc_sort_timestamp(context, 0, sort_to_bottom, false)
.await?;
if chat_id.is_protected(context).await? == ProtectionStatus::Unprotected {
let ts_start = time();
chat::add_info_msg_with_cmd(
context,
chat_id,
&stock_str::securejoin_wait(context).await,
SystemMessage::SecurejoinWait,
ts_sort,
Some(ts_start),
None,
None,
)
.await?;
chat_id.spawn_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT);
}
Ok(chat_id)
Ok(state.alice_chat())
}
}
}

View File

@@ -261,7 +261,7 @@ impl BobState {
return Ok(None);
}
};
if !self.is_msg_expected(context, step) {
if !self.is_msg_expected(context, step.as_str()) {
info!(context, "{} message out of sync for BobState", step);
return Ok(None);
}
@@ -341,15 +341,6 @@ impl BobState {
async fn send_handshake_message(&self, context: &Context, step: BobHandshakeMsg) -> Result<()> {
send_handshake_message(context, &self.invite, self.chat_id, step).await
}
/// Returns whether we are waiting for a SecureJoin message from Alice, i.e. the protocol hasn't
/// yet completed.
pub(crate) fn in_progress(&self) -> bool {
!matches!(
self.next,
SecureJoinStep::Terminated | SecureJoinStep::Completed
)
}
}
/// Sends the requested handshake message to Alice.

View File

@@ -8,7 +8,7 @@ use rusqlite::{config::DbConfig, types::ValueRef, Connection, OpenFlags, Row};
use tokio::sync::{Mutex, MutexGuard, RwLock};
use crate::blob::BlobObject;
use crate::chat::{self, add_device_msg, update_device_icon, update_saved_messages_icon};
use crate::chat::{add_device_msg, update_device_icon, update_saved_messages_icon};
use crate::config::Config;
use crate::constants::DC_CHAT_ID_TRASH;
use crate::context::Context;
@@ -289,23 +289,21 @@ impl Sql {
let passphrase_nonempty = !passphrase.is_empty();
if let Err(err) = self.try_open(context, &self.dbfile, passphrase).await {
self.close().await;
return Err(err);
}
info!(context, "Opened database {:?}.", self.dbfile);
*self.is_encrypted.write().await = Some(passphrase_nonempty);
Err(err)
} else {
info!(context, "Opened database {:?}.", self.dbfile);
*self.is_encrypted.write().await = Some(passphrase_nonempty);
// setup debug logging if there is an entry containing its id
if let Some(xdc_id) = self
.get_raw_config_u32(Config::DebugLogging.as_ref())
.await?
{
set_debug_logging_xdc(context, Some(MsgId::new(xdc_id))).await?;
// setup debug logging if there is an entry containing its id
if let Some(xdc_id) = self
.get_raw_config_u32(Config::DebugLogging.as_ref())
.await?
{
set_debug_logging_xdc(context, Some(MsgId::new(xdc_id))).await?;
}
Ok(())
}
chat::resume_securejoin_wait(context)
.await
.log_err(context)
.ok();
Ok(())
}
/// Changes the passphrase of encrypted database.
@@ -671,9 +669,10 @@ impl Sql {
/// `passphrase` is the SQLCipher database passphrase.
/// Empty string if database is not encrypted.
fn new_connection(path: &Path, passphrase: &str) -> Result<Connection> {
let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_READ_WRITE
| OpenFlags::SQLITE_OPEN_CREATE;
let mut flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
flags.insert(OpenFlags::SQLITE_OPEN_CREATE);
let conn = Connection::open_with_flags(path, flags)?;
conn.execute_batch(
"PRAGMA cipher_memory_security = OFF; -- Too slow on Android

View File

@@ -435,17 +435,6 @@ pub enum StockMessage {
#[strum(props(fallback = "%1$s reacted %2$s to \"%3$s\""))]
MsgReactedBy = 177,
#[strum(props(fallback = "Establishing guaranteed end-to-end encryption, please wait…"))]
SecurejoinWait = 190,
#[strum(props(
fallback = "Could not yet establish guaranteed end-to-end encryption, but you may already send a message."
))]
SecurejoinWaitTimeout = 191,
#[strum(props(fallback = "Contact"))]
Contact = 200,
}
impl StockMessage {
@@ -853,16 +842,6 @@ pub(crate) async fn secure_join_replies(context: &Context, contact_id: ContactId
.replace1(&contact_id.get_stock_name(context).await)
}
/// Stock string: `Establishing guaranteed end-to-end encryption, please wait…`.
pub(crate) async fn securejoin_wait(context: &Context) -> String {
translated(context, StockMessage::SecurejoinWait).await
}
/// Stock string: `Could not yet establish guaranteed end-to-end encryption, but you may already send a message.`.
pub(crate) async fn securejoin_wait_timeout(context: &Context) -> String {
translated(context, StockMessage::SecurejoinWaitTimeout).await
}
/// Stock string: `Scan to chat with %1$s`.
pub(crate) async fn setup_contact_qr_description(
context: &Context,
@@ -1101,11 +1080,6 @@ pub(crate) async fn videochat_invite_msg_body(context: &Context, url: &str) -> S
.replace1(url)
}
/// Stock string: `Contact`.
pub(crate) async fn contact(context: &Context) -> String {
translated(context, StockMessage::Contact).await
}
/// Stock string: `Error:\n\n“%1$s”`.
pub(crate) async fn configuration_failed(context: &Context, details: &str) -> String {
translated(context, StockMessage::ConfigurationFailed)

View File

@@ -2,7 +2,6 @@
use std::borrow::Cow;
use std::fmt;
use std::str;
use crate::chat::Chat;
use crate::constants::Chattype;
@@ -229,12 +228,6 @@ impl Message {
);
append_text = true;
}
Viewtype::Vcard => {
emoji = Some("👤");
type_name = Some(stock_str::contact(context).await);
type_file = None;
append_text = true;
}
Viewtype::Text | Viewtype::Unknown => {
emoji = None;
if self.param.get_cmd() == SystemMessage::LocationOnly {
@@ -347,6 +340,10 @@ mod tests {
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎵 foo.mp3").await; // file name is added for audio
let mut msg = Message::new(Viewtype::Audio);
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎵 foo.mp3").await; // file name is added for audio, empty text is not added
let mut msg = Message::new(Viewtype::Audio);
msg.set_text(some_text.clone());
msg.set_file("foo.mp3", None);
@@ -366,27 +363,6 @@ mod tests {
msg.set_file("foo.bar", None);
assert_summary_texts(&msg, ctx, "Video chat invitation").await; // text is not added for videochat invitations
let mut msg = Message::new(Viewtype::Vcard);
msg.set_file("foo.vcf", None);
assert_summary_texts(&msg, ctx, "👤 Contact").await;
msg.set_text(some_text.clone());
assert_summary_texts(&msg, ctx, "👤 bla bla").await;
let mut msg = Message::new(Viewtype::Vcard);
msg.set_file_from_bytes(
ctx,
"alice.vcf",
b"BEGIN:VCARD\n\
VERSION:4.0\n\
FN:Alice Wonderland\n\
EMAIL;TYPE=work:alice@example.org\n\
END:VCARD",
None,
)
.await
.unwrap();
assert_summary_texts(&msg, ctx, "👤 Contact").await;
// Forwarded
let mut msg = Message::new(Viewtype::Text);
msg.set_text(some_text.clone());

View File

@@ -376,7 +376,7 @@ async fn test_old_message_2() -> Result<()> {
SystemMessage::ChatProtectionEnabled
);
// This creates protection-changed info message #2 with `timestamp_sort` greater by 1.
// This creates protection-changed info message #2.
let first_email = receive_imf(
&alice,
b"From: Bob <bob@example.net>\n\
@@ -390,7 +390,8 @@ async fn test_old_message_2() -> Result<()> {
.await?
.unwrap();
// Both messages will get the same timestamp, so this one will be sorted under the previous one
// Both messages will get the same timestamp as the protection-changed
// message, so this one will be sorted under the previous one
// even though it has an older timestamp.
let second_email = receive_imf(
&alice,
@@ -406,10 +407,7 @@ async fn test_old_message_2() -> Result<()> {
.unwrap();
assert_eq!(first_email.sort_timestamp, second_email.sort_timestamp);
assert_eq!(
first_email.sort_timestamp,
protection_msg.timestamp_sort + 1
);
assert_eq!(first_email.sort_timestamp, protection_msg.timestamp_sort);
alice.golden_test_chat(chat.id, "test_old_message_2").await;

View File

@@ -93,14 +93,14 @@ pub async fn lookup_or_new(
context: &Context,
namespace: Namespace,
foreign_id: Option<ChatId>,
) -> Result<String> {
if let Some(token) = lookup(context, namespace, foreign_id).await? {
return Ok(token);
) -> String {
if let Ok(Some(token)) = lookup(context, namespace, foreign_id).await {
return token;
}
let token = create_id();
save(context, namespace, foreign_id, &token).await?;
Ok(token)
save(context, namespace, foreign_id, &token).await.ok();
token
}
pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> Result<bool> {