Compare commits

...

2 Commits

Author SHA1 Message Date
holger krekel
2d9bb929ad use automatic serialization, thanks @link2xt for the tip 2019-12-09 11:08:02 +01:00
holger krekel
84bcc81fda remove lookup_field in favor of get(HeaderDef::...) with all headers defined in headerdef.rs 2019-12-09 11:08:02 +01:00
5 changed files with 121 additions and 74 deletions

View File

@@ -12,6 +12,7 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::error::Result;
use crate::events::Event;
use crate::headerdef::HeaderDef;
use crate::job::*;
use crate::location;
use crate::message::{self, MessageState, MsgId};
@@ -60,9 +61,9 @@ pub fn dc_receive_imf(
mime_parser.unwrap()
};
if mime_parser.header.is_empty() {
// Error - even adding an empty record won't help as we do not know the message ID
warn!(context, "No header.");
if mime_parser.get(HeaderDef::From_).is_none() {
// Error - even adding an empty record won't help as we do not know the sender
warn!(context, "No From header.");
return;
}
@@ -114,7 +115,7 @@ pub fn dc_receive_imf(
}
};
if let Some(value) = mime_parser.lookup_field("Date") {
if let Some(value) = mime_parser.get(HeaderDef::Date) {
// is not yet checked against bad times! we do this later if we have the database information.
sent_timestamp = mailparse::dateparse(value).unwrap_or_default();
}
@@ -122,7 +123,7 @@ pub fn dc_receive_imf(
// get From: and check if it is known (for known From:'s we add the other To:/Cc: in the 3rd pass)
// or if From: is equal to SELF (in this case, it is any outgoing messages,
// we do not check Return-Path any more as this is unreliable, see issue #150
if let Some(field_from) = mime_parser.lookup_field("From") {
if let Some(field_from) = mime_parser.get(HeaderDef::From_) {
let mut check_self = false;
let mut from_list = Vec::with_capacity(16);
dc_add_or_lookup_contacts_by_address_list(
@@ -147,7 +148,7 @@ pub fn dc_receive_imf(
}
// Make sure, to_ids starts with the first To:-address (Cc: is added in the loop below pass)
if let Some(field) = mime_parser.lookup_field("To") {
if let Some(field) = mime_parser.get(HeaderDef::To) {
dc_add_or_lookup_contacts_by_address_list(
context,
&field,
@@ -307,7 +308,7 @@ fn add_parts(
// collect the rest information, CC: is added to the to-list, BCC: is ignored
// (we should not add BCC to groups as this would split groups. We could add them as "known contacts",
// however, the benefit is very small and this may leak data that is expected to be hidden)
if let Some(fld_cc) = mime_parser.lookup_field("Cc") {
if let Some(fld_cc) = mime_parser.get(HeaderDef::Cc) {
dc_add_or_lookup_contacts_by_address_list(
context,
fld_cc,
@@ -372,7 +373,7 @@ fn add_parts(
// handshake messages must be processed _before_ chats are created
// (eg. contacs may be marked as verified)
if mime_parser.lookup_field("Secure-Join").is_some() {
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
// avoid discarding by show_emails setting
msgrmsg = 1;
*chat_id = 0;
@@ -581,11 +582,11 @@ fn add_parts(
// if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line)
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders);
if let Some(raw) = mime_parser.lookup_field("In-Reply-To") {
if let Some(raw) = mime_parser.get(HeaderDef::InReplyTo) {
mime_in_reply_to = raw.clone();
}
if let Some(raw) = mime_parser.lookup_field("References") {
if let Some(raw) = mime_parser.get(HeaderDef::References) {
mime_references = raw.clone();
}
@@ -797,7 +798,6 @@ fn create_or_lookup_group(
to_ids: &[u32],
) -> Result<(u32, Blocked)> {
let mut chat_id_blocked = Blocked::Not;
let mut grpname = None;
let to_ids_cnt = to_ids.len();
let mut recreate_member_list = false;
let mut send_EVENT_CHAT_MODIFIED = false;
@@ -814,20 +814,21 @@ fn create_or_lookup_group(
set_better_msg(mime_parser, &better_msg);
let mut grpid = "".to_string();
if let Some(optional_field) = mime_parser.lookup_field("Chat-Group-ID") {
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupId) {
grpid = optional_field.clone();
}
if grpid.is_empty() {
if let Some(value) = mime_parser.lookup_field("Message-ID") {
if let Some(value) = mime_parser.get(HeaderDef::MessageId) {
if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(&value) {
grpid = extracted_grpid.to_string();
}
}
if grpid.is_empty() {
if let Some(extracted_grpid) = get_grpid_from_list(mime_parser, "In-Reply-To") {
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
grpid = extracted_grpid;
} else if let Some(extracted_grpid) = get_grpid_from_list(mime_parser, "References") {
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References)
{
grpid = extracted_grpid;
} else {
return create_or_lookup_adhoc_group(
@@ -846,13 +847,9 @@ fn create_or_lookup_group(
}
}
if let Some(optional_field) = mime_parser.lookup_field("Chat-Group-Name").cloned() {
grpname = Some(optional_field);
}
let field = mime_parser
.lookup_field("Chat-Group-Member-Removed")
.cloned();
if let Some(optional_field) = field {
let grpname = mime_parser.get(HeaderDef::ChatGroupName).cloned();
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupMemberRemoved).cloned() {
X_MrRemoveFromGrp = Some(optional_field);
mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup;
let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap())
@@ -868,11 +865,11 @@ fn create_or_lookup_group(
from_id as u32,
)
} else {
let field = mime_parser.lookup_field("Chat-Group-Member-Added").cloned();
let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned();
if let Some(optional_field) = field {
X_MrAddToGrp = Some(optional_field);
mime_parser.is_system_message = SystemMessage::MemberAddedToGroup;
if let Some(optional_field) = mime_parser.lookup_field("Chat-Group-Image").cloned() {
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupImage).cloned() {
X_MrGrpImageChanged = optional_field;
}
better_msg = context.stock_system_msg(
@@ -882,7 +879,7 @@ fn create_or_lookup_group(
from_id as u32,
)
} else {
let field = mime_parser.lookup_field("Chat-Group-Name-Changed");
let field = mime_parser.get(HeaderDef::ChatGroupNameChanged);
if let Some(field) = field {
X_MrGrpNameChanged = true;
better_msg = context.stock_system_msg(
@@ -897,8 +894,7 @@ fn create_or_lookup_group(
);
mime_parser.is_system_message = SystemMessage::GroupNameChanged;
} else if let Some(optional_field) =
mime_parser.lookup_field("Chat-Group-Image").cloned()
} else if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupImage).cloned()
{
// fld_value is a pointer somewhere into mime_parser, must not be freed
X_MrGrpImageChanged = optional_field;
@@ -954,7 +950,7 @@ fn create_or_lookup_group(
|| X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap()))
{
// group does not exist but should be created
let create_verified = if mime_parser.lookup_field("Chat-Verified").is_some() {
let create_verified = if mime_parser.get(HeaderDef::ChatVerified).is_some() {
if let Err(err) =
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
{
@@ -1130,8 +1126,8 @@ fn create_or_lookup_group(
}
/// try extract a grpid from a message-id list header value
fn get_grpid_from_list(mime_parser: &MimeParser, header_key: &str) -> Option<String> {
if let Some(value) = mime_parser.lookup_field(header_key) {
fn extract_grpid(mime_parser: &MimeParser, headerdef: HeaderDef) -> Option<String> {
if let Some(value) = mime_parser.get(headerdef) {
for part in value.split(',').map(str::trim) {
if !part.is_empty() {
if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(part) {
@@ -1486,13 +1482,13 @@ fn is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> boo
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
`In-Reply-To`/`References:` (to support non-Delta-Clients) */
if let Some(field) = mime_parser.lookup_field("In-Reply-To") {
if let Some(field) = mime_parser.get(HeaderDef::InReplyTo) {
if is_known_rfc724_mid_in_list(context, &field) {
return true;
}
}
if let Some(field) = mime_parser.lookup_field("References") {
if let Some(field) = mime_parser.get(HeaderDef::References) {
if is_known_rfc724_mid_in_list(context, &field) {
return true;
}
@@ -1538,13 +1534,13 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: &mailparse::MailAddr) -> b
/// - it is okay, if the referenced messages are moved to trash here
/// - no check for the Chat-* headers (function is only called if it is no messenger message itself)
fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeParser) -> bool {
if let Some(value) = mime_parser.lookup_field("In-Reply-To") {
if let Some(value) = mime_parser.get(HeaderDef::InReplyTo) {
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
return true;
}
}
if let Some(value) = mime_parser.lookup_field("References") {
if let Some(value) = mime_parser.get(HeaderDef::References) {
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
return true;
}
@@ -1683,9 +1679,9 @@ mod tests {
\n\
hello\x00";
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
assert_eq!(get_grpid_from_list(&mimeparser, "In-Reply-To"), None);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
let grpid = Some("HcxyMARjyJy".to_string());
assert_eq!(get_grpid_from_list(&mimeparser, "References"), grpid);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
}
#[test]
@@ -1699,7 +1695,7 @@ mod tests {
hello\x00";
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
let grpid = Some("HcxyMARjyJy".to_string());
assert_eq!(get_grpid_from_list(&mimeparser, "In-Reply-To"), grpid);
assert_eq!(get_grpid_from_list(&mimeparser, "References"), grpid);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
}
}

43
src/headerdef.rs Normal file
View File

@@ -0,0 +1,43 @@
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
#[allow(dead_code)]
pub enum HeaderDef {
MessageId,
Subject,
Date,
From_,
To,
Cc,
Disposition,
OriginalMessageId,
ListId,
References,
InReplyTo,
Precedence,
ChatVersion,
ChatGroupId,
ChatGroupName,
ChatGroupNameChanged,
ChatVerified,
ChatGroupImage,
ChatVoiceMessage,
ChatGroupMemberRemoved,
ChatGroupMemberAdded,
ChatContent,
ChatDuration,
ChatDispositionNotificationTo,
AutocryptSetupMessage,
SecureJoin,
SecureJoinGroup,
SecureJoinFingerprint,
SecureJoinInvitenumber,
SecureJoinAuth,
_TestHeader,
}
impl HeaderDef {
/// Returns the corresponding Event id.
pub fn get_headername(&self) -> String {
self.to_string()
}
}

View File

@@ -33,6 +33,8 @@ mod log;
#[macro_use]
pub mod error;
pub mod headerdef;
pub(crate) mod events;
pub use events::*;

View File

@@ -14,6 +14,7 @@ use crate::dc_simplify::*;
use crate::dc_tools::*;
use crate::e2ee;
use crate::error::Result;
use crate::headerdef::HeaderDef;
use crate::job::{job_add, Action};
use crate::location;
use crate::message;
@@ -28,7 +29,7 @@ use crate::{bail, ensure};
pub struct MimeParser<'a> {
pub context: &'a Context,
pub parts: Vec<Part>,
pub header: HashMap<String, String>,
header: HashMap<String, String>,
pub decrypting_failed: bool,
pub signatures: HashSet<String>,
pub gossipped_addr: HashSet<String>,
@@ -144,7 +145,7 @@ impl<'a> MimeParser<'a> {
}
fn parse_headers(&mut self) -> Result<()> {
if self.lookup_field("Autocrypt-Setup-Message").is_some() {
if self.get(HeaderDef::AutocryptSetupMessage).is_some() {
let has_setup_file = self.parts.iter().any(|p| {
p.mimetype.is_some() && p.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
});
@@ -175,12 +176,12 @@ impl<'a> MimeParser<'a> {
}
}
}
} else if let Some(value) = self.lookup_field("Chat-Content") {
} else if let Some(value) = self.get(HeaderDef::ChatContent) {
if value == "location-streaming-enabled" {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
}
}
if self.lookup_field("Chat-Group-Image").is_some() && !self.parts.is_empty() {
if self.get(HeaderDef::ChatGroupImage).is_some() && !self.parts.is_empty() {
let textpart = &self.parts[0];
if textpart.typ == Viewtype::Text && self.parts.len() >= 2 {
let imgpart = &mut self.parts[1];
@@ -255,13 +256,13 @@ impl<'a> MimeParser<'a> {
}
if self.parts.len() == 1 {
if self.parts[0].typ == Viewtype::Audio
&& self.lookup_field("Chat-Voice-Message").is_some()
&& self.get(HeaderDef::ChatVoiceMessage).is_some()
{
let part_mut = &mut self.parts[0];
part_mut.typ = Viewtype::Voice;
}
if self.parts[0].typ == Viewtype::Image {
if let Some(value) = self.lookup_field("Chat-Content") {
if let Some(value) = self.get(HeaderDef::ChatContent) {
if value == "sticker" {
let part_mut = &mut self.parts[0];
part_mut.typ = Viewtype::Sticker;
@@ -273,7 +274,7 @@ impl<'a> MimeParser<'a> {
|| part.typ == Viewtype::Voice
|| part.typ == Viewtype::Video
{
if let Some(field_0) = self.lookup_field("Chat-Duration") {
if let Some(field_0) = self.get(HeaderDef::ChatDuration) {
let duration_ms = field_0.parse().unwrap_or_default();
if duration_ms > 0 && duration_ms < 24 * 60 * 60 * 1000 {
let part_mut = &mut self.parts[0];
@@ -283,12 +284,12 @@ impl<'a> MimeParser<'a> {
}
}
if !self.decrypting_failed {
if let Some(dn_field) = self.lookup_field("Chat-Disposition-Notification-To") {
if let Some(dn_field) = self.get(HeaderDef::ChatDispositionNotificationTo) {
if self.get_last_nonmeta().is_some() {
let addrs = mailparse::addrparse(&dn_field).unwrap();
if let Some(dn_to_addr) = addrs.first() {
if let Some(from_field) = self.lookup_field("From") {
if let Some(from_field) = self.get(HeaderDef::From_) {
let from_addrs = mailparse::addrparse(&from_field).unwrap();
if let Some(from_addr) = from_addrs.first() {
@@ -338,7 +339,7 @@ impl<'a> MimeParser<'a> {
}
pub(crate) fn get_subject(&self) -> Option<String> {
if let Some(s) = self.header.get("subject") {
if let Some(s) = self.get(HeaderDef::Subject) {
if s.is_empty() {
return None;
}
@@ -348,8 +349,8 @@ impl<'a> MimeParser<'a> {
}
}
pub fn lookup_field(&self, field_name: &str) -> Option<&String> {
self.header.get(&field_name.to_lowercase())
pub fn get(&self, headerdef: HeaderDef) -> Option<&String> {
self.header.get(&headerdef.get_headername())
}
fn parse_mime_recursive(&mut self, mail: &mailparse::ParsedMail<'_>) -> Result<bool> {
@@ -649,11 +650,11 @@ impl<'a> MimeParser<'a> {
}
pub fn is_mailinglist_message(&self) -> bool {
if self.lookup_field("List-Id").is_some() {
if self.get(HeaderDef::ListId).is_some() {
return true;
}
if let Some(precedence) = self.lookup_field("Precedence") {
if let Some(precedence) = self.get(HeaderDef::Precedence) {
precedence == "list" || precedence == "bulk"
} else {
false
@@ -662,7 +663,7 @@ impl<'a> MimeParser<'a> {
pub fn sender_equals_recipient(&self) -> bool {
/* get From: and check there is exactly one sender */
if let Some(field) = self.lookup_field("From") {
if let Some(field) = self.get(HeaderDef::From_) {
if let Ok(addrs) = mailparse::addrparse(field) {
if addrs.len() != 1 {
return false;
@@ -693,11 +694,11 @@ impl<'a> MimeParser<'a> {
}
pub fn get_rfc724_mid(&self) -> Option<String> {
// get Message-ID from header
if let Some(field) = self.lookup_field("Message-ID") {
return parse_message_id(field);
if let Some(msgid) = self.get(HeaderDef::MessageId) {
parse_message_id(msgid)
} else {
None
}
None
}
fn hash_header(&mut self, fields: &[mailparse::MailHeader<'_>]) {
@@ -727,9 +728,10 @@ impl<'a> MimeParser<'a> {
let (report_fields, _) = mailparse::parse_headers(&report_body)?;
// must be present
if let Some(_disposition) = report_fields.get_first_value("Disposition").ok().flatten() {
let disp = HeaderDef::Disposition.get_headername();
if let Some(_disposition) = report_fields.get_first_value(&disp).ok().flatten() {
if let Some(original_message_id) = report_fields
.get_first_value("Original-Message-ID")
.get_first_value(&HeaderDef::OriginalMessageId.get_headername())
.ok()
.flatten()
.and_then(|v| parse_message_id(&v))
@@ -1108,14 +1110,14 @@ mod tests {
let raw = b"From: hello\n\
Content-Type: multipart/mixed; boundary=\"==break==\";\n\
Subject: outer-subject\n\
X-Special-A: special-a\n\
Foo: Bar\nChat-Version: 0.0\n\
Secure-Join-Group: no\n\
Test-Header: Bar\nChat-Version: 0.0\n\
\n\
--==break==\n\
Content-Type: text/plain; protected-headers=\"v1\";\n\
Subject: inner-subject\n\
X-Special-B: special-b\n\
Foo: Xy\n\
SecureBar-Join-Group: yes\n\
Test-Header: Xy\n\
Chat-Version: 1.0\n\
\n\
test1\n\
@@ -1125,15 +1127,18 @@ mod tests {
\x00";
let mimeparser = MimeParser::from_bytes(&context.ctx, &raw[..]).unwrap();
// test that we treat Subject as a protected header that can
// bubble upwards
assert_eq!(mimeparser.get_subject(), Some("inner-subject".into()));
let of = mimeparser.lookup_field("X-Special-A").unwrap();
assert_eq!(of, "special-a");
let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap();
assert_eq!(of, "no");
let of = mimeparser.lookup_field("Foo").unwrap();
// unprotected headers do not bubble upwards
let of = mimeparser.get(HeaderDef::_TestHeader).unwrap();
assert_eq!(of, "Bar");
let of = mimeparser.lookup_field("Chat-Version").unwrap();
let of = mimeparser.get(HeaderDef::ChatVersion).unwrap();
assert_eq!(of, "1.0");
assert_eq!(mimeparser.parts.len(), 1);
}

View File

@@ -11,6 +11,7 @@ use crate::context::Context;
use crate::e2ee::*;
use crate::error::Error;
use crate::events::Event;
use crate::headerdef::HeaderDef;
use crate::key::*;
use crate::lot::LotState;
use crate::message::Message;
@@ -350,7 +351,7 @@ pub(crate) fn handle_securejoin_handshake(
"handle_securejoin_handshake(): called with special contact id"
);
let step = mimeparser
.lookup_field("Secure-Join")
.get(HeaderDef::SecureJoin)
.ok_or_else(|| format_err!("This message is not a Secure-Join message"))?;
info!(
@@ -378,7 +379,7 @@ pub(crate) fn handle_securejoin_handshake(
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
let invitenumber = match mimeparser.lookup_field("Secure-Join-Invitenumber") {
let invitenumber = match mimeparser.get(HeaderDef::SecureJoinInvitenumber) {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
@@ -467,7 +468,7 @@ pub(crate) fn handle_securejoin_handshake(
==== Step 6 in "Out-of-band verified groups" protocol ====
============================================================ */
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mimeparser.lookup_field("Secure-Join-Fingerprint") {
let fingerprint = match mimeparser.get(HeaderDef::SecureJoinFingerprint) {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
@@ -496,7 +497,7 @@ pub(crate) fn handle_securejoin_handshake(
}
info!(context, "Fingerprint verified.",);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0 = match mimeparser.lookup_field("Secure-Join-Auth") {
let auth_0 = match mimeparser.get(HeaderDef::SecureJoinAuth) {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
@@ -526,7 +527,7 @@ pub(crate) fn handle_securejoin_handshake(
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = mimeparser
.lookup_field("Secure-Join-Group")
.get(HeaderDef::SecureJoinGroup)
.map(|s| s.as_str())
.unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, field_grpid);
@@ -600,7 +601,7 @@ pub(crate) fn handle_securejoin_handshake(
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
.lookup_field("Chat-Group-Member-Added")
.get(HeaderDef::ChatGroupMemberAdded)
.map(|s| s.as_str())
.unwrap_or_else(|| "");
if join_vg && !addr_equals_self(context, cg_member_added) {
@@ -635,7 +636,7 @@ pub(crate) fn handle_securejoin_handshake(
inviter_progress!(context, contact_id, 800);
inviter_progress!(context, contact_id, 1000);
let field_grpid = mimeparser
.lookup_field("Secure-Join-Group")
.get(HeaderDef::SecureJoinGroup)
.map(|s| s.as_str())
.unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);