use Chat-User-Avatar and Chat-Group-Avatar

we change the name on the wire as the old Chat-Group-Image header
could not be used on random mails, it was the marker for a "Changed" message,
if we would keep this names, things will fail for exising installations
as messages are dropped and a "Group image changed" message is shown instead.
This commit is contained in:
B. Petersen
2019-12-11 02:44:16 +01:00
committed by holger krekel
parent 7c3d8356c4
commit d681fa6cba
8 changed files with 52 additions and 54 deletions

24
spec.md
View File

@@ -1,6 +1,6 @@
# Chat-over-Email specification # Chat-over-Email specification
Version 0.19.0 Version 0.20.0
This document describes how emails can be used This document describes how emails can be used
to implement typical messenger functions to implement typical messenger functions
@@ -248,11 +248,11 @@ and the message SHOULD appear as a message or action from the sender.
A group MAY have a group-image. A group MAY have a group-image.
To change or set the group-image, To change or set the group-image,
the messenger MUST attach an image file to a message the messenger MUST attach an image file to a message
and MUST add the header `Chat-Group-Image` and MUST add the header `Chat-Group-Avatar`
with the value set to the image name. with the value set to the image name.
To remove the group-image, To remove the group-image,
the messenger MUST add the header `Chat-Group-Image: 0`. the messenger MUST add the header `Chat-Group-Avatar: 0`.
The messenger SHOULD send an explicit mail for each group image change. The messenger SHOULD send an explicit mail for each group image change.
The body of the message SHOULD contain The body of the message SHOULD contain
@@ -265,7 +265,7 @@ and the message SHOULD appear as a message or action from the sender.
Chat-Version: 1.0 Chat-Version: 1.0
Chat-Group-ID: 12345uvwxyZ Chat-Group-ID: 12345uvwxyZ
Chat-Group-Name: Our Group Chat-Group-Name: Our Group
Chat-Group-Image: image.jpg Chat-Group-Avatar: image.jpg
Message-ID: Gr.12345uvwxyZ.0005@domain Message-ID: Gr.12345uvwxyZ.0005@domain
Subject: Chat: Our Group: Hello, ... Subject: Chat: Our Group: Hello, ...
Content-Type: multipart/mixed; boundary="==break==" Content-Type: multipart/mixed; boundary="==break=="
@@ -283,7 +283,7 @@ and the message SHOULD appear as a message or action from the sender.
The image format SHOULD be image/jpeg or image/png. The image format SHOULD be image/jpeg or image/png.
To save data, it is RECOMMENDED To save data, it is RECOMMENDED
to add a `Chat-Group-Image` only on image changes. to add a `Chat-Group-Avatar` only on image changes.
# Set profile image # Set profile image
@@ -291,17 +291,17 @@ to add a `Chat-Group-Image` only on image changes.
A user MAY have a profile-image that MAY be spread to their contacts. A user MAY have a profile-image that MAY be spread to their contacts.
To change or set the profile-image, To change or set the profile-image,
the messenger MUST attach an image file to a message the messenger MUST attach an image file to a message
and MUST add the header `Chat-Profile-Image` and MUST add the header `Chat-User-Avatar`
with the value set to the image name. with the value set to the image name.
To remove the profile-image, To remove the profile-image,
the messenger MUST add the header `Chat-Profile-Image: 0`. the messenger MUST add the header `Chat-User-Avatar: 0`.
To spread the image, To spread the image,
the messenger MAY send the profile image the messenger MAY send the profile image
together with the next mail to a given contact together with the next mail to a given contact
(to do this only once, (to do this only once,
the messenger has to keep a `profile_image_update_state` somewhere). the messenger has to keep a `user_avatar_update_state` somewhere).
Alternatively, the messenger MAY send an explicit mail Alternatively, the messenger MAY send an explicit mail
for each profile-image change to all contacts using a compatible messenger. for each profile-image change to all contacts using a compatible messenger.
The messenger SHOULD NOT send an explicit mail to normal MUAs. The messenger SHOULD NOT send an explicit mail to normal MUAs.
@@ -309,7 +309,7 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
From: sender@domain From: sender@domain
To: rcpt@domain To: rcpt@domain
Chat-Version: 1.0 Chat-Version: 1.0
Chat-Profile-Image: photo.jpg Chat-User-Avatar: photo.jpg
Subject: Chat: Hello, ... Subject: Chat: Hello, ...
Content-Type: multipart/mixed; boundary="==break==" Content-Type: multipart/mixed; boundary="==break=="
@@ -325,10 +325,10 @@ The messenger SHOULD NOT send an explicit mail to normal MUAs.
--==break==-- --==break==--
The image format SHOULD be image/jpeg or image/png. The image format SHOULD be image/jpeg or image/png.
Note that `Chat-Profile-Image` may appear together with all other headers, Note that `Chat-User-Avatar` may appear together with all other headers,
eg. there may be a `Chat-Profile-Image` and a `Chat-Group-Image` header eg. there may be a `Chat-User-Avatar` and a `Chat-Group-Avatar` header
in the same message. in the same message.
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header To save data, it is RECOMMENDED to add a `Chat-User-Avatar` header
only on image changes. only on image changes.

View File

@@ -15,7 +15,7 @@ use crate::events::Event;
use crate::key::*; use crate::key::*;
use crate::login_param::LoginParam; use crate::login_param::LoginParam;
use crate::message::{MessageState, MsgId}; use crate::message::{MessageState, MsgId};
use crate::mimeparser::ImageAction; use crate::mimeparser::AvatarAction;
use crate::param::*; use crate::param::*;
use crate::peerstate::*; use crate::peerstate::*;
use crate::sql; use crate::sql;
@@ -966,21 +966,21 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
pub fn set_profile_image( pub fn set_profile_image(
context: &Context, context: &Context,
contact_id: u32, contact_id: u32,
profile_image: ImageAction, profile_image: AvatarAction,
) -> Result<()> { ) -> Result<()> {
// the given profile image is expected to be already in the blob directory // the given profile image is expected to be already in the blob directory
// as profile images can be set only by receiving messages, this should be always the case, however. // as profile images can be set only by receiving messages, this should be always the case, however.
let mut contact = Contact::load_from_db(context, contact_id)?; let mut contact = Contact::load_from_db(context, contact_id)?;
let changed = match profile_image { let changed = match profile_image {
ImageAction::Change(profile_image) => { AvatarAction::Change(profile_image) => {
contact.param.set(Param::ProfileImage, profile_image); contact.param.set(Param::ProfileImage, profile_image);
true true
} }
ImageAction::Delete => { AvatarAction::Delete => {
contact.param.remove(Param::ProfileImage); contact.param.remove(Param::ProfileImage);
true true
} }
ImageAction::None => false, AvatarAction::None => false,
}; };
if changed { if changed {
contact.update_param(context)?; contact.update_param(context)?;

View File

@@ -237,8 +237,8 @@ pub fn dc_receive_imf(
); );
} }
if mime_parser.profile_image != ImageAction::None { if mime_parser.user_avatar != AvatarAction::None {
match contact::set_profile_image(&context, from_id, mime_parser.profile_image) { match contact::set_profile_image(&context, from_id, mime_parser.user_avatar) {
Ok(()) => { Ok(()) => {
context.call_cb(Event::ChatModified(chat_id)); context.call_cb(Event::ChatModified(chat_id));
true true
@@ -898,13 +898,13 @@ fn create_or_lookup_group(
mime_parser.is_system_message = SystemMessage::GroupNameChanged; mime_parser.is_system_message = SystemMessage::GroupNameChanged;
} else if let Some(value) = mime_parser.get(HeaderDef::ChatContent).cloned() { } else if let Some(value) = mime_parser.get(HeaderDef::ChatContent).cloned() {
if value == "group-avatar-changed" && mime_parser.group_avatar != ImageAction::None if value == "group-avatar-changed" && mime_parser.group_avatar != AvatarAction::None
{ {
// this is just an explicit message containing the group-avatar, // this is just an explicit message containing the group-avatar,
// apart from that, the group-avatar is send along with various other messages // apart from that, the group-avatar is send along with various other messages
mime_parser.is_system_message = SystemMessage::GroupImageChanged; mime_parser.is_system_message = SystemMessage::GroupImageChanged;
better_msg = context.stock_system_msg( better_msg = context.stock_system_msg(
if mime_parser.group_avatar == ImageAction::Delete { if mime_parser.group_avatar == AvatarAction::Delete {
StockMessage::MsgGrpImgDeleted StockMessage::MsgGrpImgDeleted
} else { } else {
StockMessage::MsgGrpImgChanged StockMessage::MsgGrpImgChanged
@@ -1036,19 +1036,19 @@ fn create_or_lookup_group(
} }
} }
} }
if mime_parser.group_avatar != ImageAction::None { if mime_parser.group_avatar != AvatarAction::None {
info!(context, "group-avatar change for {}", chat_id); info!(context, "group-avatar change for {}", chat_id);
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
match &mime_parser.group_avatar { match &mime_parser.group_avatar {
ImageAction::Change(profile_image) => { AvatarAction::Change(profile_image) => {
chat.param.set(Param::ProfileImage, profile_image); chat.param.set(Param::ProfileImage, profile_image);
true true
} }
ImageAction::Delete => { AvatarAction::Delete => {
chat.param.remove(Param::ProfileImage); chat.param.remove(Param::ProfileImage);
true true
} }
ImageAction::None => false, AvatarAction::None => false,
}; };
chat.update_param(context)?; chat.update_param(context)?;
send_EVENT_CHAT_MODIFIED = true; send_EVENT_CHAT_MODIFIED = true;

View File

@@ -20,7 +20,7 @@ pub enum HeaderDef {
ChatGroupNameChanged, ChatGroupNameChanged,
ChatVerified, ChatVerified,
ChatGroupAvatar, ChatGroupAvatar,
ChatProfileImage, ChatUserAvatar,
ChatVoiceMessage, ChatVoiceMessage,
ChatGroupMemberRemoved, ChatGroupMemberRemoved,
ChatGroupMemberAdded, ChatGroupMemberAdded,

View File

@@ -895,13 +895,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
Some(path) => match build_selfavatar_file(context, path) { Some(path) => match build_selfavatar_file(context, path) {
Ok((part, filename)) => { Ok((part, filename)) => {
parts.push(part); parts.push(part);
protected_headers.push(Header::new("Chat-Profile-Image".into(), filename)) protected_headers.push(Header::new("Chat-User-Avatar".into(), filename))
} }
Err(err) => warn!(context, "mimefactory: cannot attach selfavatar: {}", err), Err(err) => warn!(context, "mimefactory: cannot attach selfavatar: {}", err),
}, },
None => { None => protected_headers.push(Header::new("Chat-User-Avatar".into(), "0".into())),
protected_headers.push(Header::new("Chat-Profile-Image".into(), "0".into()))
}
} }
} }

View File

@@ -37,15 +37,15 @@ pub struct MimeParser<'a> {
pub is_system_message: SystemMessage, pub is_system_message: SystemMessage,
pub location_kml: Option<location::Kml>, pub location_kml: Option<location::Kml>,
pub message_kml: Option<location::Kml>, pub message_kml: Option<location::Kml>,
pub profile_image: ImageAction, pub user_avatar: AvatarAction,
pub group_avatar: ImageAction, pub group_avatar: AvatarAction,
reports: Vec<Report>, reports: Vec<Report>,
mdns_enabled: bool, mdns_enabled: bool,
parsed_protected_headers: bool, parsed_protected_headers: bool,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ImageAction { pub enum AvatarAction {
None, None,
Delete, Delete,
Change(String), Change(String),
@@ -92,8 +92,8 @@ impl<'a> MimeParser<'a> {
is_system_message: SystemMessage::Unknown, is_system_message: SystemMessage::Unknown,
location_kml: None, location_kml: None,
message_kml: None, message_kml: None,
profile_image: ImageAction::None, user_avatar: AvatarAction::None,
group_avatar: ImageAction::None, group_avatar: AvatarAction::None,
mdns_enabled, mdns_enabled,
parsed_protected_headers: false, parsed_protected_headers: false,
}; };
@@ -194,11 +194,11 @@ impl<'a> MimeParser<'a> {
} }
if let Some(header_value) = self.get(HeaderDef::ChatGroupAvatar).cloned() { if let Some(header_value) = self.get(HeaderDef::ChatGroupAvatar).cloned() {
self.group_avatar = self.image_action_from_header(header_value); self.group_avatar = self.avatar_action_from_header(header_value);
} }
if let Some(header_value) = self.get(HeaderDef::ChatProfileImage).cloned() { if let Some(header_value) = self.get(HeaderDef::ChatUserAvatar).cloned() {
self.profile_image = self.image_action_from_header(header_value); self.user_avatar = self.avatar_action_from_header(header_value);
} }
if self.has_chat_version() && self.parts.len() == 2 { if self.has_chat_version() && self.parts.len() == 2 {
@@ -332,9 +332,9 @@ impl<'a> MimeParser<'a> {
Ok(()) Ok(())
} }
fn image_action_from_header(&mut self, header_value: String) -> ImageAction { fn avatar_action_from_header(&mut self, header_value: String) -> AvatarAction {
if header_value == "0" { if header_value == "0" {
return ImageAction::Delete; return AvatarAction::Delete;
} else { } else {
let mut i = 0; let mut i = 0;
while i != self.parts.len() { while i != self.parts.len() {
@@ -342,7 +342,7 @@ impl<'a> MimeParser<'a> {
if let Some(part_filename) = &part.org_filename { if let Some(part_filename) = &part.org_filename {
if part_filename == &header_value { if part_filename == &header_value {
if let Some(blob) = part.param.get(Param::File) { if let Some(blob) = part.param.get(Param::File) {
let res = ImageAction::Change(blob.to_string()); let res = AvatarAction::Change(blob.to_string());
self.parts.remove(i); self.parts.remove(i);
return res; return res;
} }
@@ -352,7 +352,7 @@ impl<'a> MimeParser<'a> {
i += 1; i += 1;
} }
} }
ImageAction::None AvatarAction::None
} }
pub fn get_last_nonmeta(&self) -> Option<&Part> { pub fn get_last_nonmeta(&self) -> Option<&Part> {
@@ -1164,25 +1164,25 @@ mod tests {
} }
#[test] #[test]
fn test_mimeparser_with_profile_image() { fn test_mimeparser_with_user_avatar() {
let t = dummy_context(); let t = dummy_context();
let raw = include_bytes!("../test-data/message/mail_attach_txt.eml"); let raw = include_bytes!("../test-data/message/mail_attach_txt.eml");
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap(); let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
assert_eq!(mimeparser.profile_image, ImageAction::None); assert_eq!(mimeparser.user_avatar, AvatarAction::None);
assert_eq!(mimeparser.group_avatar, ImageAction::None); assert_eq!(mimeparser.group_avatar, AvatarAction::None);
let raw = include_bytes!("../test-data/message/mail_with_profile_image.eml"); let raw = include_bytes!("../test-data/message/mail_with_user_avatar.eml");
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap(); let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts.len(), 1);
assert_ne!(mimeparser.profile_image, ImageAction::None); assert_ne!(mimeparser.user_avatar, AvatarAction::None);
assert_ne!(mimeparser.profile_image, ImageAction::Delete); assert_ne!(mimeparser.user_avatar, AvatarAction::Delete);
assert_eq!(mimeparser.group_avatar, ImageAction::None); assert_eq!(mimeparser.group_avatar, AvatarAction::None);
let raw = include_bytes!("../test-data/message/mail_with_profile_image_deleted.eml"); let raw = include_bytes!("../test-data/message/mail_with_user_avatar_deleted.eml");
let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap(); let mimeparser = MimeParser::from_bytes(&t.ctx, &raw[..]).unwrap();
assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts.len(), 1);
assert_eq!(mimeparser.profile_image, ImageAction::Delete); assert_eq!(mimeparser.user_avatar, AvatarAction::Delete);
assert_eq!(mimeparser.group_avatar, ImageAction::None); assert_eq!(mimeparser.group_avatar, AvatarAction::None);
} }
} }

View File

@@ -1,4 +1,4 @@
Chat-Profile-Image: avatar.png Chat-User-Avatar: avatar.png
Subject: =?utf-8?q?Chat=3A_this_is_a_message_with_a_=2E=2E=2E?= Subject: =?utf-8?q?Chat=3A_this_is_a_message_with_a_=2E=2E=2E?=
Message-ID: Mr.wOBwZNbBTVt.NZpmQDwWoNk@example.org Message-ID: Mr.wOBwZNbBTVt.NZpmQDwWoNk@example.org
In-Reply-To: Mr.ETXqza5-WpB.zDEYOLECxAw@example.org In-Reply-To: Mr.ETXqza5-WpB.zDEYOLECxAw@example.org

View File

@@ -1,5 +1,5 @@
Content-Type: text/plain; charset=utf-8 Content-Type: text/plain; charset=utf-8
Chat-Profile-Image: 0 Chat-User-Avatar: 0
Subject: =?utf-8?q?Chat=3A_profile_image_deleted?= Subject: =?utf-8?q?Chat=3A_profile_image_deleted?=
Message-ID: Mr.tsgoJgn-cBf.0TkFWKJzeSp@example.org Message-ID: Mr.tsgoJgn-cBf.0TkFWKJzeSp@example.org
Date: Sun, 08 Dec 2019 23:28:30 +0000 Date: Sun, 08 Dec 2019 23:28:30 +0000