mirror of
https://github.com/chatmail/core.git
synced 2026-04-20 06:56:29 +03:00
> _greetings from the ice of the deutsche bahn 🚂🚃🚃🚃 always a pleasure to see how well delta chat meanwhile performs in bad networks :)_ this PR adds an API to request other chat members to replace the message text of an already sent message. scope is mainly to fix typos. this feature is known from whatsapp, telegram, signal, and is [requested](https://support.delta.chat/t/retract-edit-sent-messages/1918) [since](https://support.delta.chat/t/edit-messages-in-delta-chat/899) [years](https://github.com/deltachat/deltachat-android/issues/198). technically, a message with an [`Obsoletes:`](https://datatracker.ietf.org/doc/html/rfc2076#section-3.6) header is sent out. ``` From: alice@nine To: bob@nine Message-ID: 2000@nine In-Reply-To: 1000@nine Obsoletes: 1000@nine Edited: this is the new text ``` the body is the new text, prefixed by the static text `Edited:` (which is not a header). the latter is to make the message appear more nicely in Non-Delta-MUA. save for the `In-Reply-To` header. the `Edited:` prefix is removed by Delta Chat on receiving. headers should be protected and moved to e2ee part as usual. corrected message text is flagged, and UI should show this state, in practise as "Edited" beside the date. in case, the original message is not found, nothing happens and the correction message is trashes (assuming the original was deleted). question: is the `Obsoletes:` header a good choice? i _thought_ there is some more specifica RFC, but i cannot find sth. in any case, it should be an header that is not used otherwise by MUA, to make sure no wanted messages disappear. what is NOT done and out of scope: - optimise if messages are not yet sent out. this is doable, but introduces quite some cornercaes and may not be worth the effort - replaces images or other attachments. this is also a bit cornercasy and beyond "typo fixing", and better be handled by "delete for me and others" (which may come soon, having the idea now, it seems easy :) - get edit history in any way. not sure if this is worth the effort, remember, as being a private messenger, we assume trust among chat members. it is also questionable wrt privacy, seized devices etc. - add text where nothing was before; again, scope is "typo fixing", better avoid cornercases - saved messages are not edited (this is anyway questionable) - quoted texts, that are used for the case the original message is deleted, are not updated - edits are ignored when the original message is not there yet (out of order, not yet downloaded) - message status indicator does not show if edits are sent out or not - similar to reactions, webxdc updates, sync messages. signal has the same issue :) still, connectivity should show if there are messages pending <img width="366" alt="Screenshot 2025-02-17 at 17 25 02" src="https://github.com/user-attachments/assets/a4a53996-438b-47ef-9004-2c9062eea5d7" /> corresponding iOS branch (no PR yet): https://github.com/deltachat/deltachat-ios/compare/main...r10s/edit-messages --------- Co-authored-by: l <link2xt@testrun.org>
173 lines
4.8 KiB
Rust
173 lines
4.8 KiB
Rust
//! # List of email headers.
|
|
|
|
use mailparse::{MailHeader, MailHeaderMap};
|
|
|
|
#[derive(Debug, Display, Clone, PartialEq, Eq, IntoStaticStr)]
|
|
#[strum(serialize_all = "kebab_case")]
|
|
#[allow(missing_docs)]
|
|
pub enum HeaderDef {
|
|
MessageId,
|
|
Subject,
|
|
Date,
|
|
From_,
|
|
To,
|
|
AutoSubmitted,
|
|
|
|
/// Carbon copy.
|
|
Cc,
|
|
Disposition,
|
|
|
|
/// Used in the "Body Part Header" of MDNs as of RFC 8098.
|
|
/// Indicates the Message-ID of the message for which the MDN is being issued.
|
|
OriginalMessageId,
|
|
|
|
/// Delta Chat extension for message IDs in combined MDNs
|
|
AdditionalMessageIds,
|
|
|
|
/// Outlook-SMTP-server replace the `Message-ID:`-header
|
|
/// and write the original ID to `X-Microsoft-Original-Message-ID`.
|
|
/// To sort things correctly and to not show outgoing messages twice,
|
|
/// we need to check that header as well.
|
|
XMicrosoftOriginalMessageId,
|
|
|
|
/// Thunderbird header used to store Draft information.
|
|
///
|
|
/// Thunderbird 78.11.0 does not set \Draft flag on messages saved as "Template", but sets this
|
|
/// header, so it can be used to ignore such messages.
|
|
XMozillaDraftInfo,
|
|
|
|
/// Mailing list ID defined in [RFC 2919](https://tools.ietf.org/html/rfc2919).
|
|
ListId,
|
|
ListPost,
|
|
|
|
/// List-Help header defined in [RFC 2369](https://datatracker.ietf.org/doc/html/rfc2369).
|
|
ListHelp,
|
|
References,
|
|
|
|
/// In-Reply-To header containing Message-ID of the parent message.
|
|
InReplyTo,
|
|
|
|
/// Used to detect mailing lists if contains "list" value
|
|
/// as described in [RFC 3834](https://tools.ietf.org/html/rfc3834)
|
|
Precedence,
|
|
|
|
ContentType,
|
|
ContentId,
|
|
ChatVersion,
|
|
ChatGroupId,
|
|
ChatGroupName,
|
|
ChatGroupNameChanged,
|
|
ChatVerified,
|
|
ChatGroupAvatar,
|
|
ChatUserAvatar,
|
|
ChatVoiceMessage,
|
|
ChatGroupMemberRemoved,
|
|
ChatGroupMemberAdded,
|
|
ChatContent,
|
|
|
|
/// Past members of the group.
|
|
ChatGroupPastMembers,
|
|
|
|
/// Space-separated timestamps of member addition
|
|
/// for members listed in the `To` field
|
|
/// followed by timestamps of member removal
|
|
/// for members listed in the `Chat-Group-Past-Members` field.
|
|
ChatGroupMemberTimestamps,
|
|
|
|
/// Duration of the attached media file.
|
|
ChatDuration,
|
|
|
|
ChatDispositionNotificationTo,
|
|
ChatWebrtcRoom,
|
|
|
|
/// This message obsoletes the text of the message defined here by rfc724_mid.
|
|
ChatEdit,
|
|
|
|
/// [Autocrypt](https://autocrypt.org/) header.
|
|
Autocrypt,
|
|
AutocryptGossip,
|
|
AutocryptSetupMessage,
|
|
SecureJoin,
|
|
|
|
/// Deprecated header containing Group-ID in `vg-request-with-auth` message.
|
|
///
|
|
/// It is not used by Alice as Alice knows the group corresponding to the AUTH token.
|
|
/// Bob still sends it for backwards compatibility.
|
|
SecureJoinGroup,
|
|
SecureJoinFingerprint,
|
|
SecureJoinInvitenumber,
|
|
SecureJoinAuth,
|
|
Sender,
|
|
|
|
/// Ephemeral message timer.
|
|
EphemeralTimer,
|
|
Received,
|
|
|
|
/// A header that includes the results of the DKIM, SPF and DMARC checks.
|
|
/// See <https://datatracker.ietf.org/doc/html/rfc8601>
|
|
AuthenticationResults,
|
|
|
|
/// Node address from iroh where direct addresses have been removed.
|
|
IrohNodeAddr,
|
|
|
|
/// Advertised gossip topic for one webxdc.
|
|
IrohGossipTopic,
|
|
|
|
#[cfg(test)]
|
|
TestHeader,
|
|
}
|
|
|
|
impl HeaderDef {
|
|
/// Returns the corresponding header string.
|
|
pub fn get_headername(&self) -> &'static str {
|
|
self.into()
|
|
}
|
|
}
|
|
|
|
#[allow(missing_docs)]
|
|
pub trait HeaderDefMap {
|
|
/// Returns requested header value if it exists.
|
|
fn get_header_value(&self, headerdef: HeaderDef) -> Option<String>;
|
|
|
|
/// Returns requested header if it exists.
|
|
fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader>;
|
|
}
|
|
|
|
impl HeaderDefMap for [MailHeader<'_>] {
|
|
fn get_header_value(&self, headerdef: HeaderDef) -> Option<String> {
|
|
self.get_first_value(headerdef.get_headername())
|
|
}
|
|
fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader> {
|
|
self.get_first_header(headerdef.get_headername())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
/// Test that kebab_case serialization works as expected
|
|
fn kebab_test() {
|
|
assert_eq!(HeaderDef::From_.get_headername(), "from");
|
|
|
|
assert_eq!(HeaderDef::TestHeader.get_headername(), "test-header");
|
|
}
|
|
|
|
#[test]
|
|
/// Test that headers are parsed case-insensitively
|
|
fn test_get_header_value_case() {
|
|
let (headers, _) =
|
|
mailparse::parse_headers(b"fRoM: Bob\naUtoCryPt-SeTup-MessAge: v99").unwrap();
|
|
assert_eq!(
|
|
headers.get_header_value(HeaderDef::AutocryptSetupMessage),
|
|
Some("v99".to_string())
|
|
);
|
|
assert_eq!(
|
|
headers.get_header_value(HeaderDef::From_),
|
|
Some("Bob".to_string())
|
|
);
|
|
assert_eq!(headers.get_header_value(HeaderDef::Autocrypt), None);
|
|
}
|
|
}
|