mirror of
https://github.com/chatmail/core.git
synced 2026-05-06 06:46:35 +03:00
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:
committed by
holger krekel
parent
7c3d8356c4
commit
d681fa6cba
24
spec.md
24
spec.md
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub enum HeaderDef {
|
|||||||
ChatGroupNameChanged,
|
ChatGroupNameChanged,
|
||||||
ChatVerified,
|
ChatVerified,
|
||||||
ChatGroupAvatar,
|
ChatGroupAvatar,
|
||||||
ChatProfileImage,
|
ChatUserAvatar,
|
||||||
ChatVoiceMessage,
|
ChatVoiceMessage,
|
||||||
ChatGroupMemberRemoved,
|
ChatGroupMemberRemoved,
|
||||||
ChatGroupMemberAdded,
|
ChatGroupMemberAdded,
|
||||||
|
|||||||
@@ -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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user