message: improve attachment heuristics
Do not consider calender-invitations "attachments"; do mark as "calendar". Do recognize application/ics as calendar messages. Update mime-object to expose a message part's disposition. Change the "is-attachment" heuristic to include inline parts if they have a filename parameter in their content-disposition. Note that this doesn't change things radically; the delta is +69 and -202 for ~6500 attachments.
This commit is contained in:
@ -195,17 +195,22 @@ MessagePart::looks_like_attachment() const noexcept
|
||||
if (!ctype)
|
||||
return false; // no content-type: not an attachment.
|
||||
|
||||
// we consider some parts _not_ to be attachments regardless of disposition
|
||||
if (matches(*ctype,{{"application", "pgp-keys"}}))
|
||||
// we consider some parts _not_ to be attachments regardless of
|
||||
// disposition
|
||||
if (matches(*ctype,{{"application", "pgp-keys"},
|
||||
{"application", "ics"}}))
|
||||
return false;
|
||||
|
||||
// we consider some parts to be attachments regardless of disposition
|
||||
if (matches(*ctype,{{"image", "*"},
|
||||
{"audio", "*"},
|
||||
{"application", "*"},
|
||||
{"application", "x-patch"}}))
|
||||
if (is_attachment()) // i.e., as per content-disposition
|
||||
return true;
|
||||
|
||||
// we also consider "inline" parts as attachment, if the
|
||||
// content-disposition has a filename property.
|
||||
if (const auto cdisp{mime_object().content_disposition()}; !!cdisp) {
|
||||
if (const auto& fname{cdisp->parameter("filename")}; !!fname)
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, rely on the disposition
|
||||
return is_attachment();
|
||||
}
|
||||
|
||||
@ -424,9 +424,11 @@ process_part(const MimeObject& parent, const MimePart& part,
|
||||
if (!ctype)
|
||||
return;
|
||||
|
||||
// flag as calendar, if not already
|
||||
// flag as calendar, if not already.
|
||||
if (none_of(info.flags & Flags::Calendar) &&
|
||||
ctype->is_type("text", "calendar"))
|
||||
(ctype->is_type("text", "calendar") ||
|
||||
ctype->is_type("text", "x-vcalendar") ||
|
||||
ctype->is_type("application", "ics")))
|
||||
info.flags |= Flags::Calendar;
|
||||
|
||||
// flag as attachment, if not already.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
** Copyright (C) 2022-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
** Copyright (C) 2022-2026 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
@ -171,7 +171,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeContentType
|
||||
*
|
||||
@ -197,7 +196,6 @@ struct MimeContentType: public Object {
|
||||
return g_mime_content_type_is_type(self(), type.c_str(),
|
||||
subtype.c_str());
|
||||
}
|
||||
|
||||
Option<std::string> parameter(const std::string& name) const noexcept {
|
||||
const char *param{g_mime_content_type_get_parameter(self(), name.c_str())};
|
||||
if (!param || !param[0])
|
||||
@ -205,17 +203,47 @@ struct MimeContentType: public Object {
|
||||
else
|
||||
return Some(std::string{param});
|
||||
}
|
||||
|
||||
private:
|
||||
GMimeContentType* self() const {
|
||||
return reinterpret_cast<GMimeContentType*>(object());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeContentDisposition
|
||||
*
|
||||
*/
|
||||
struct MimeContentDisposition: public Object {
|
||||
|
||||
MimeContentDisposition(GMimeContentDisposition *disp) : Object{G_OBJECT(disp)} {
|
||||
if (!GMIME_IS_CONTENT_DISPOSITION(self()))
|
||||
throw std::runtime_error("not a content-disposition");
|
||||
}
|
||||
|
||||
std::string disposition() const noexcept {
|
||||
if (const auto disp{g_mime_content_disposition_get_disposition(self())}; disp)
|
||||
return disp;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
bool is_attachment() const noexcept {
|
||||
return g_mime_content_disposition_is_attachment(self());
|
||||
}
|
||||
|
||||
Option<std::string> parameter(const std::string& name) const noexcept {
|
||||
const char *param{g_mime_content_disposition_get_parameter(self(), name.c_str())};
|
||||
if (!param || !param[0])
|
||||
return Nothing;
|
||||
else
|
||||
return Some(std::string{param});
|
||||
}
|
||||
private:
|
||||
GMimeContentDisposition* self() const {
|
||||
return reinterpret_cast<GMimeContentDisposition*>(object());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeStream
|
||||
*
|
||||
@ -272,7 +300,6 @@ constexpr Option<std::string_view> to_string_view_opt(const S& seq, T t) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeDataWrapper
|
||||
*
|
||||
@ -298,7 +325,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeCertifcate
|
||||
*
|
||||
@ -494,7 +520,6 @@ constexpr Option<std::string_view> to_string_view_opt(MimeCertificate::Validity
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeSignature
|
||||
*
|
||||
@ -575,7 +600,6 @@ static inline std::string to_string(MimeSignature::Status status) {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeDecryptResult
|
||||
*
|
||||
@ -645,7 +669,6 @@ constexpr Option<std::string_view> to_string_view_opt(MimeDecryptResult::CipherA
|
||||
return to_string_view_opt(AllCipherAlgos, algo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeCryptoContext
|
||||
*
|
||||
@ -748,7 +771,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeObject
|
||||
*
|
||||
@ -819,6 +841,19 @@ public:
|
||||
g_mime_object_get_content_type_parameter(self(), param.c_str()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content disposition
|
||||
*
|
||||
* @return the content-disposition or Nothing
|
||||
*/
|
||||
Option<MimeContentDisposition> content_disposition() const noexcept {
|
||||
auto disp{g_mime_object_get_content_disposition(self())};
|
||||
if (!disp)
|
||||
return Nothing;
|
||||
else
|
||||
return MimeContentDisposition(disp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this MimeObject to some stream
|
||||
*
|
||||
@ -918,7 +953,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeMessage
|
||||
*
|
||||
@ -1024,7 +1058,6 @@ private:
|
||||
return reinterpret_cast<GMimeMessage*>(object());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimePart.
|
||||
*
|
||||
@ -1200,7 +1233,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeMessagePart.
|
||||
*
|
||||
@ -1235,7 +1267,7 @@ private:
|
||||
}
|
||||
|
||||
};
|
||||
/**
|
||||
/**
|
||||
* Thin wrapper around a GMimeApplicationPkcs7Mime
|
||||
*
|
||||
*/
|
||||
@ -1270,7 +1302,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeMultiPart
|
||||
*
|
||||
@ -1327,7 +1358,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeMultiPartEncrypted
|
||||
*
|
||||
@ -1365,7 +1395,6 @@ private:
|
||||
|
||||
MU_ENABLE_BITOPS(MimeMultipartEncrypted::DecryptFlags);
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around a GMimeMultiPartSigned
|
||||
*
|
||||
@ -1388,8 +1417,6 @@ public:
|
||||
EnableOnlineCertificateChecks = GMIME_VERIFY_ENABLE_ONLINE_CERTIFICATE_CHECKS
|
||||
};
|
||||
|
||||
// Result<std::vector<MimeSignature>> verify(VerifyFlags vflags=VerifyFlags::None) const noexcept;
|
||||
|
||||
Result<std::vector<MimeSignature>> verify(const MimeCryptoContext& ctx,
|
||||
VerifyFlags vflags=VerifyFlags::None) const noexcept;
|
||||
|
||||
|
||||
@ -814,8 +814,7 @@ RU5EOlZFVkVOVA0KRU5EOlZDQUxFTkRBUg0K
|
||||
g_assert_true(!!message);
|
||||
assert_equal(message->subject(),
|
||||
"Invitation: HELLO, @ Thu 9 Jan 2014 08:30 - 09:30 (william@example.com)");
|
||||
g_assert_true(message->flags() == (Flags::Passed|Flags::Seen|
|
||||
Flags::HasAttachment|Flags::Calendar));
|
||||
g_assert_true(message->flags() == (Flags::Passed|Flags::Seen|Flags::Calendar));
|
||||
g_assert_cmpuint(message->body_html().value_or("").find("DETAILS"), ==, 2271);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user