Compare commits

..

16 Commits

Author SHA1 Message Date
holger krekel
d3800bb08e fix #1077 for unknown senders in a group chat (such as mailer daemons): don't recreate member list and show a special stockstring-ed message advising to hit "more info". 2019-12-20 11:29:55 +01:00
Floris Bruynooghe
ec40dd1b6f Change the JSON API function to be from a serialised struct
This is the first experiment towards using structs to define the API.
It adds it as a new method on the existing Chat struct.

The types in this struct could improve as well as many other things.
But this is a straight forward translation of the existing json! macro
into a struct.
2019-12-20 10:39:39 +01:00
Alexander Krotov
f2f8898004 fix(mimefactory): wrap base64-encoded attachments to 78 columns
RFC5322 requires emails to be wrapped to 78 columns excluding CRLF.
2019-12-20 09:11:35 +01:00
björn petersen
3afa37c6ea Merge pull request #1075 from deltachat/prepare_beta20
prep 1.0.0-beta.20
2019-12-19 17:17:26 +01:00
holger krekel
8c0ce38301 prep 1.0.0-beta.20 2019-12-19 16:55:24 +01:00
holger krekel
cc6aa3209c fix the fix 2019-12-19 16:53:55 +01:00
holger krekel
76a86763dd address @r10s comment 2019-12-19 16:53:55 +01:00
holger krekel
09fb039528 another reverse mut bites the dust 2019-12-19 16:53:55 +01:00
holger krekel
174d3300c4 not sure it's much better but using a static-sized array is probably better than a dynamically sized vec, thanks @dignifiedquire 2019-12-19 16:53:55 +01:00
holger krekel
8b57ce1792 remove unused include_in_contactlist 2019-12-19 16:53:55 +01:00
holger krekel
6c14e429eb Origin::is_verified() -> Origin::is_known() because this has nothing to do with verified groups or contacts 2019-12-19 16:53:55 +01:00
holger krekel
5f200c6bc3 don't pass incoming_origin as &mut as the caller doesn't need it 2019-12-19 16:53:55 +01:00
holger krekel
d52347ee1d also don't pass "to_id" and don't make it mut inside add_parts 2019-12-19 16:53:55 +01:00
holger krekel
d0d9aa4400 - move CC-parsing next to To-parsing where it blongs
- pass to_ids and from_id as immutable to add_parts
2019-12-19 16:53:55 +01:00
holger krekel
c3d909c818 add a test that contacts are properly created and fix ordering in dc_receive_imf to pass the test 2019-12-19 16:53:55 +01:00
dignifiedquire
d9e718b0e0 feat(imap): update async-imap for oauth2 fix 2019-12-19 13:51:59 +01:00
9 changed files with 196 additions and 66 deletions

View File

@@ -1,5 +1,13 @@
# Changelog
## 1.0.0-beta.20
- #1074 fix OAUTH2/gmail
- #1072 fix group members not appearing in contact list
- #1071 never block interrupt_idle (thus hopefully also not on maybe_network())
- #1069 reduce smtp-timeout to 30 seconds
- #1066 #1065 avoid unwrap in dehtml, make literals more readable
## 1.0.0-beta.19
- #1058 timeout smtp-send if it doesn't complete in 15 minutes

10
Cargo.lock generated
View File

@@ -85,7 +85,7 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.1.1"
source = "git+https://github.com/async-email/async-imap?branch=dcc-stable#4f0852971c3f397c2b8a343ef1378992c02f61de"
source = "git+https://github.com/async-email/async-imap?branch=dcc-stable#50e843113e3a67e924a8a14c477833da3ebc1b44"
dependencies = [
"async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -96,6 +96,7 @@ dependencies = [
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures_codec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"imap-proto 0.9.1 (git+https://github.com/djc/tokio-imap)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -657,7 +658,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.19"
version = "1.0.0-beta.20"
dependencies = [
"async-imap 0.1.1 (git+https://github.com/async-email/async-imap?branch=dcc-stable)",
"async-native-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -734,14 +735,15 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.19"
version = "1.0.0-beta.20"
dependencies = [
"deltachat 1.0.0-beta.19",
"deltachat 1.0.0-beta.20",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-beta.19"
version = "1.0.0-beta.20"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.19"
version = "1.0.0-beta.20"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
@@ -21,6 +21,7 @@ libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
failure = "0.1.6"
serde_json = "1.0"
[features]
default = ["vendored", "nightly", "ringbuf"]

View File

@@ -10,6 +10,7 @@
#[macro_use]
extern crate human_panic;
extern crate num_traits;
extern crate serde_json;
use std::collections::HashMap;
use std::convert::TryInto;
@@ -2390,12 +2391,27 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match chat::get_info_json(ctx, chat_id) {
Ok(s) => s.strdup(),
Err(err) => {
error!(ctx, "get_info_json({}) returned: {}", chat_id, err);
return "".strdup();
}
.with_inner(|ctx| {
let chat = match chat::Chat::load_from_db(ctx, chat_id) {
Ok(chat) => chat,
Err(err) => {
error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err);
return "".strdup();
}
};
let info = match chat.get_info(ctx) {
Ok(info) => info,
Err(err) => {
error!(
ctx,
"dc_get_chat_info_json() failed to get chat info: {}", err
);
return "".strdup();
}
};
serde_json::to_string(&info)
.unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json")
.strdup()
})
.unwrap_or_else(|_| "".strdup())
}

View File

@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use itertools::Itertools;
use num_traits::FromPrimitive;
use serde_json::json;
use serde::{Deserialize, Serialize};
use crate::blob::{BlobError, BlobObject};
use crate::chatlist::*;
@@ -240,6 +240,30 @@ impl Chat {
color
}
/// Returns a struct describing the current state of the chat.
///
/// This is somewhat experimental, even more so than the rest of
/// deltachat, and the data returned is still subject to change.
pub fn get_info(&self, context: &Context) -> Result<ChatInfo, Error> {
let draft = match get_draft(context, self.id)? {
Some(message) => message.text.unwrap_or_else(String::new),
_ => String::new(),
};
Ok(ChatInfo {
id: self.id,
type_: self.typ as u32,
name: self.name.clone(),
archived: self.archived,
param: self.param.to_string(),
gossiped_timestamp: self.get_gossiped_timestamp(context),
is_sending_locations: self.is_sending_locations,
color: self.get_color(context),
profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new),
subtitle: self.get_subtitle(context),
draft,
})
}
/// Returns true if the chat is archived.
pub fn is_archived(&self) -> bool {
self.archived
@@ -497,6 +521,68 @@ impl Chat {
}
}
/// The current state of a chat.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChatInfo {
/// The chat ID.
pub id: u32,
/// The type of chat as a `u32` representation of [Chattype].
///
/// On the C API this number is one of the
/// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`,
/// `DC_CHAT_TYPE_GROUP` or `DC_CHAT_TYPE_VERIFIED_GROUP`
/// constants.
#[serde(rename = "type")]
pub type_: u32,
/// The name of the chat.
pub name: String,
/// Whether the chat is archived.
pub archived: bool,
/// The "params" of the chat.
///
/// This is the string-serialised version of [Params] currently.
pub param: String,
/// Something to do with gossiping and timestamps?
pub gossiped_timestamp: i64,
/// Whether this chat is currently sending location-stream messages.
pub is_sending_locations: bool,
/// Colour this chat should be represented in by the UI.
///
/// Yes, spelling colour is hard.
pub color: u32,
/// The path to the profile image.
///
/// If there is no profile image set this will be an empty string
/// currently.
pub profile_image: PathBuf,
/// Subtitle for the chat.
pub subtitle: String,
/// The draft message text.
///
/// If the chat has not draft this is an empty string.
///
/// TODO: This doesn't seem rich enough, it can not handle drafts
/// which contain non-text parts. Perhaps it should be a
/// simple `has_draft` bool instead.
pub draft: String,
// ToDo:
// - [ ] deaddrop,
// - [ ] summary,
// - [ ] lastUpdated,
// - [ ] freshMessageCounter,
// - [ ] email
}
/// Create a normal chat or a group chat by a messages ID that comes typically
/// from the deaddrop, DC_CHAT_ID_DEADDROP (1).
///
@@ -1990,54 +2076,6 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul
Ok(())
}
pub fn get_info_json(context: &Context, chat_id: u32) -> Result<String, Error> {
let chat = Chat::load_from_db(context, chat_id).unwrap();
// ToDo:
// - [x] id
// - [x] type
// - [x] name
// - [x] archived
// - [x] color
// - [x] profileImage
// - [x] subtitle
// - [x] draft,
// - [ ] deaddrop,
// - [ ] summary,
// - [ ] lastUpdated,
// - [ ] freshMessageCounter,
// - [ ] email
let profile_image = match chat.get_profile_image(context) {
Some(path) => path.into_os_string().into_string().unwrap(),
None => "".to_string(),
};
let draft = match get_draft(context, chat_id) {
Ok(message) => match message {
Some(m) => m.text.unwrap_or_else(|| "".to_string()),
None => "".to_string(),
},
Err(_) => "".to_string(),
};
let s = json!({
"id": chat.id,
"type": chat.typ as u32,
"name": chat.name,
"archived": chat.archived,
"param": chat.param.to_string(),
"gossiped_timestamp": chat.get_gossiped_timestamp(context),
"is_sending_locations": chat.is_sending_locations,
"color": chat.get_color(context),
"profile_image": profile_image,
"subtitle": chat.get_subtitle(context),
"draft": draft
});
Ok(s.to_string())
}
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize {
context
.sql
@@ -2192,8 +2230,41 @@ pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
mod tests {
use super::*;
use crate::contact::Contact;
use crate::test_utils::*;
#[test]
fn test_chat_info() {
let t = dummy_context();
let bob = Contact::create(&t.ctx, "bob", "bob@example.com").unwrap();
let chat_id = create_by_contact_id(&t.ctx, bob).unwrap();
let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap();
let info = chat.get_info(&t.ctx).unwrap();
// Ensure we can serialise this.
println!("{}", serde_json::to_string_pretty(&info).unwrap());
let expected = r#"
{
"id": 10,
"type": 100,
"name": "bob",
"archived": false,
"param": "",
"gossiped_timestamp": 0,
"is_sending_locations": false,
"color": 15895624,
"profile_image": "",
"subtitle": "bob@example.com",
"draft": ""
}
"#;
// Ensure we can deserialise this.
let loaded: ChatInfo = serde_json::from_str(expected).unwrap();
assert_eq!(info, loaded);
}
#[test]
fn test_get_draft_no_draft() {
let t = dummy_context();

View File

@@ -877,10 +877,15 @@ fn create_or_lookup_group(
mime_parser.repl_msg_by_error(s);
}
}
// check if the sender is a member of the existing group -
// if not, we'll recreate the group list
if !chat::is_contact_in_chat(context, chat_id, from_id as u32) {
recreate_member_list = true;
// The From-address is not part of this group.
// It could be a new user or a DSN from a mailer-daemon.
// in any case we do not want to recreate the member list
// but still show the message as part of the chat.
// After all, the sender has a reference/in-reply-to that
// points to this chat.
let s = context.stock_str(StockMessage::UnknownSenderForChat);
mime_parser.repl_msg_by_error(s.to_string());
}
}

View File

@@ -1002,6 +1002,21 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
}
}
/// Returns base64-encoded buffer `buf` split into 78-bytes long
/// chunks separated by CRLF.
///
/// This line length limit is an
/// [RFC5322 requirement](https://tools.ietf.org/html/rfc5322#section-2.1.1).
fn wrapped_base64_encode(buf: &[u8]) -> String {
let base64 = base64::encode(&buf);
let mut chars = base64.chars();
(0..)
.map(|_| chars.by_ref().take(78).collect::<String>())
.take_while(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("\r\n")
}
fn build_body_file(
context: &Context,
msg: &Message,
@@ -1062,7 +1077,7 @@ fn build_body_file(
};
let body = std::fs::read(blob.to_abs_path())?;
let encoded_body = base64::encode(&body);
let encoded_body = wrapped_base64_encode(&body);
let mail = PartBuilder::new()
.content_type(&mimetype)
@@ -1084,7 +1099,7 @@ fn build_selfavatar_file(context: &Context, path: String) -> Result<(PartBuilder
None => mime::APPLICATION_OCTET_STREAM,
};
let body = std::fs::read(blob.to_abs_path())?;
let encoded_body = base64::encode(&body);
let encoded_body = wrapped_base64_encode(&body);
let part = PartBuilder::new()
.content_type(&mimetype)
@@ -1203,4 +1218,13 @@ mod tests {
"<123@q> <456@d>".to_string()
);
}
#[test]
fn test_wrapped_base64_encode() {
let input = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let output =
"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU\r\n\
FBQUFBQUFBQQ==";
assert_eq!(wrapped_base64_encode(input), output);
}
}

View File

@@ -185,6 +185,9 @@ pub enum StockMessage {
Recipients don't need to install Delta Chat, visit websites or sign up anywhere - \
however, of course, if they like, you may point them to 👉 https://get.delta.chat"))]
WelcomeMessage = 71,
#[strum(props(fallback = "Unknown Sender for this chat. See 'info' for more details."))]
UnknownSenderForChat = 72,
}
/*