message: update implementation
Add more of the Message class (and various helpers), which are to replace all the `mu-msg-*` code. Add more tests.
This commit is contained in:
@ -20,6 +20,8 @@ lib_mu_message=static_library(
|
|||||||
[
|
[
|
||||||
'mu-message.cc',
|
'mu-message.cc',
|
||||||
'mu-message.hh',
|
'mu-message.hh',
|
||||||
|
'mu-message-part.cc',
|
||||||
|
'mu-message-part.hh',
|
||||||
'mu-contact.hh',
|
'mu-contact.hh',
|
||||||
'mu-contact.cc',
|
'mu-contact.cc',
|
||||||
'mu-document.cc',
|
'mu-document.cc',
|
||||||
@ -29,7 +31,9 @@ lib_mu_message=static_library(
|
|||||||
'mu-flags.hh',
|
'mu-flags.hh',
|
||||||
'mu-flags.cc',
|
'mu-flags.cc',
|
||||||
'mu-priority.hh',
|
'mu-priority.hh',
|
||||||
'mu-priority.cc'
|
'mu-priority.cc',
|
||||||
|
'mu-mime-object.cc',
|
||||||
|
'mu-mime-object.hh'
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
glib_dep,
|
glib_dep,
|
||||||
@ -39,12 +43,11 @@ lib_mu_message=static_library(
|
|||||||
lib_mu_utils_dep],
|
lib_mu_utils_dep],
|
||||||
install: false)
|
install: false)
|
||||||
|
|
||||||
# some of the libme headers include xapian
|
|
||||||
xapian_incs = xapian_dep.get_pkgconfig_variable('includedir')
|
|
||||||
lib_mu_message_dep = declare_dependency(
|
lib_mu_message_dep = declare_dependency(
|
||||||
link_with: lib_mu_message,
|
link_with: lib_mu_message,
|
||||||
|
dependencies: [ xapian_dep, gmime_dep ],
|
||||||
include_directories:
|
include_directories:
|
||||||
include_directories(['.', '..', xapian_incs]))
|
include_directories(['.', '..']))
|
||||||
|
|
||||||
#
|
#
|
||||||
# tests
|
# tests
|
||||||
@ -77,6 +80,14 @@ test('test-flags',
|
|||||||
install: false,
|
install: false,
|
||||||
cpp_args: ['-DBUILD_TESTS'],
|
cpp_args: ['-DBUILD_TESTS'],
|
||||||
dependencies: [glib_dep, gmime_dep, lib_mu_message_dep]))
|
dependencies: [glib_dep, gmime_dep, lib_mu_message_dep]))
|
||||||
|
|
||||||
|
test('test-message',
|
||||||
|
executable('test-message',
|
||||||
|
'mu-message.cc',
|
||||||
|
install: false,
|
||||||
|
cpp_args: ['-DBUILD_TESTS'],
|
||||||
|
dependencies: [glib_dep, gmime_dep, lib_mu_message_dep]))
|
||||||
|
|
||||||
test('test-priority',
|
test('test-priority',
|
||||||
executable('test-priority',
|
executable('test-priority',
|
||||||
'mu-priority.cc',
|
'mu-priority.cc',
|
||||||
|
|||||||
@ -36,7 +36,7 @@ Contact::display_name() const
|
|||||||
|
|
||||||
Mu::Contacts
|
Mu::Contacts
|
||||||
Mu::make_contacts(InternetAddressList* addr_lst,
|
Mu::make_contacts(InternetAddressList* addr_lst,
|
||||||
Field::Id field_id, ::time_t message_date)
|
Field::Id field_id, int64_t message_date)
|
||||||
{
|
{
|
||||||
Contacts contacts;
|
Contacts contacts;
|
||||||
size_t num{};
|
size_t num{};
|
||||||
@ -70,7 +70,7 @@ Mu::make_contacts(InternetAddressList* addr_lst,
|
|||||||
Mu::Contacts
|
Mu::Contacts
|
||||||
Mu::make_contacts(const std::string& addrs,
|
Mu::make_contacts(const std::string& addrs,
|
||||||
Field::Id field_id,
|
Field::Id field_id,
|
||||||
::time_t message_date)
|
int64_t message_date)
|
||||||
{
|
{
|
||||||
auto addr_list = internet_address_list_parse(NULL, addrs.c_str());
|
auto addr_list = internet_address_list_parse(NULL, addrs.c_str());
|
||||||
if (!addr_list) {
|
if (!addr_list) {
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
|
#include <utils/mu-option.hh>
|
||||||
#include "mu-fields.hh"
|
#include "mu-fields.hh"
|
||||||
|
|
||||||
struct _InternetAddressList;
|
struct _InternetAddressList;
|
||||||
@ -54,7 +55,7 @@ struct Contact {
|
|||||||
* @param message_date_ data for the message for this contact
|
* @param message_date_ data for the message for this contact
|
||||||
*/
|
*/
|
||||||
Contact(const std::string& email_, const std::string& name_ = "",
|
Contact(const std::string& email_, const std::string& name_ = "",
|
||||||
std::optional<Field::Id> field_id_ = {},
|
Option<Field::Id> field_id_ = {},
|
||||||
time_t message_date_ = 0)
|
time_t message_date_ = 0)
|
||||||
: email{email_}, name{name_}, field_id{field_id_},
|
: email{email_}, name{name_}, field_id{field_id_},
|
||||||
message_date{message_date_}, personal{}, frequency{1}, tstamp{}
|
message_date{message_date_}, personal{}, frequency{1}, tstamp{}
|
||||||
@ -101,7 +102,7 @@ struct Contact {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a hash-value for this contact, which gets lazily calculated. This
|
* Get a hash-value for this contact, which gets lazily calculated. This
|
||||||
* is for use with container classes. This uses the _lowercase_ email
|
* * is for use with container classes. This uses the _lowercase_ email
|
||||||
* address.
|
* address.
|
||||||
*
|
*
|
||||||
* @return the hash
|
* @return the hash
|
||||||
@ -118,14 +119,14 @@ struct Contact {
|
|||||||
* data members
|
* data members
|
||||||
*/
|
*/
|
||||||
|
|
||||||
std::string email; /**< Email address for this contact.Not empty */
|
std::string email; /**< Email address for this contact.Not empty */
|
||||||
std::string name; /**< Name for this contact; can be empty. */
|
std::string name; /**< Name for this contact; can be empty. */
|
||||||
std::optional<Field::Id> field_id; /**< Field Id of contact or nullopt */
|
Option<Field::Id> field_id; /**< Field Id of contact or nullopt */
|
||||||
::time_t message_date; /**< date of the message from which the
|
int64_t message_date; /**< date of the message from which the
|
||||||
* contact originates */
|
* contact originates (or 0) */
|
||||||
bool personal; /**< A personal message? */
|
bool personal; /**< A personal message? */
|
||||||
size_t frequency; /**< Frequency of this contact */
|
size_t frequency; /**< Frequency of this contact */
|
||||||
int64_t tstamp; /**< Timestamp for this contact */
|
int64_t tstamp; /**< Timestamp for this contact (internal use) */
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cleanup_name() { // replace control characters by spaces.
|
void cleanup_name() { // replace control characters by spaces.
|
||||||
@ -149,7 +150,7 @@ using Contacts = std::vector<Contact>;
|
|||||||
*/
|
*/
|
||||||
Contacts
|
Contacts
|
||||||
make_contacts(/*const*/ struct _InternetAddressList* addr_lst,
|
make_contacts(/*const*/ struct _InternetAddressList* addr_lst,
|
||||||
Field::Id field_id, ::time_t message_date);
|
Field::Id field_id, int64_t message_date);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a sequence of Contact objects from an InternetAddressList
|
* Create a sequence of Contact objects from an InternetAddressList
|
||||||
@ -162,7 +163,7 @@ make_contacts(/*const*/ struct _InternetAddressList* addr_lst,
|
|||||||
*/
|
*/
|
||||||
Contacts
|
Contacts
|
||||||
make_contacts(const std::string& addrs,
|
make_contacts(const std::string& addrs,
|
||||||
Field::Id field_id, ::time_t message_date);
|
Field::Id field_id, int64_t message_date);
|
||||||
} // namespace Mu
|
} // namespace Mu
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -69,8 +69,10 @@ Document::add(Field::Id id, const std::string& val)
|
|||||||
void
|
void
|
||||||
Document::add(Field::Id id, const std::vector<std::string>& vals)
|
Document::add(Field::Id id, const std::vector<std::string>& vals)
|
||||||
{
|
{
|
||||||
const auto field{field_from_id(id)};
|
if (vals.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto field{field_from_id(id)};
|
||||||
if (field.is_value())
|
if (field.is_value())
|
||||||
xdoc_.add_value(field.value_no(), Mu::join(vals, SepaChar1));
|
xdoc_.add_value(field.value_no(), Mu::join(vals, SepaChar1));
|
||||||
|
|
||||||
@ -88,6 +90,9 @@ Document::string_vec_value(Field::Id field_id) const noexcept
|
|||||||
void
|
void
|
||||||
Document::add(Field::Id id, const Contacts& contacts)
|
Document::add(Field::Id id, const Contacts& contacts)
|
||||||
{
|
{
|
||||||
|
if (contacts.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
const auto field{field_from_id(id)};
|
const auto field{field_from_id(id)};
|
||||||
std::vector<std::string> cvec;
|
std::vector<std::string> cvec;
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
#include "mu-priority.hh"
|
#include "mu-priority.hh"
|
||||||
#include "mu-flags.hh"
|
#include "mu-flags.hh"
|
||||||
#include "mu-contact.hh"
|
#include "mu-contact.hh"
|
||||||
|
#include <utils/mu-option.hh>
|
||||||
|
|
||||||
namespace Mu {
|
namespace Mu {
|
||||||
|
|
||||||
@ -93,7 +94,6 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* updating a document with terms & values
|
* updating a document with terms & values
|
||||||
*/
|
*/
|
||||||
@ -107,7 +107,7 @@ public:
|
|||||||
void add(Field::Id field_id, const std::string& val);
|
void add(Field::Id field_id, const std::string& val);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a string-vec value to the document
|
* Add a string-vec value to the document, if non-empty
|
||||||
*
|
*
|
||||||
* @param field_id field id
|
* @param field_id field id
|
||||||
* @param val string-vec value
|
* @param val string-vec value
|
||||||
@ -116,13 +116,14 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add message-contacts to the document
|
* Add message-contacts to the document, if non-empty
|
||||||
*
|
*
|
||||||
* @param field_id field id
|
* @param field_id field id
|
||||||
* @param contacts message contacts
|
* @param contacts message contacts
|
||||||
*/
|
*/
|
||||||
void add(Field::Id id, const Contacts& contacts);
|
void add(Field::Id id, const Contacts& contacts);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an integer value to the document
|
* Add an integer value to the document
|
||||||
*
|
*
|
||||||
@ -140,17 +141,27 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add message flags to the document
|
* Add message flags to the document
|
||||||
*
|
*
|
||||||
* @param flags mesage flags.
|
* @param flags mesage flags.
|
||||||
*/
|
*/
|
||||||
void add(Flags flags);
|
void add(Flags flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generically adds an optional value, if set, to the document
|
||||||
|
*
|
||||||
|
* @param id the field 0d
|
||||||
|
* @param an optional value
|
||||||
|
*/
|
||||||
|
template<typename T> void add(Field::Id id, const Option<T>& val) {
|
||||||
|
if (val)
|
||||||
|
add(id, val.value());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Retrieving values
|
* Retrieving values
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a message-field as a string-value
|
* Get a message-field as a string-value
|
||||||
*
|
*
|
||||||
|
|||||||
@ -24,9 +24,9 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
|
||||||
#include <xapian.h>
|
#include <xapian.h>
|
||||||
#include <utils/mu-utils.hh>
|
#include <utils/mu-utils.hh>
|
||||||
|
#include <utils/mu-option.hh>
|
||||||
|
|
||||||
namespace Mu {
|
namespace Mu {
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ static constexpr std::array<Field, Field::id_size()>
|
|||||||
Field::Type::String,
|
Field::Type::String,
|
||||||
"msgid",
|
"msgid",
|
||||||
"Attachment MIME-type",
|
"Attachment MIME-type",
|
||||||
"mime:image/jpeg",
|
"msgid:abc@123",
|
||||||
'i',
|
'i',
|
||||||
Field::Flag::GMime |
|
Field::Flag::GMime |
|
||||||
Field::Flag::NormalTerm |
|
Field::Flag::NormalTerm |
|
||||||
@ -502,11 +502,11 @@ void field_for_each(Func&& func) {
|
|||||||
* @return a message-field id, or nullopt if not found.
|
* @return a message-field id, or nullopt if not found.
|
||||||
*/
|
*/
|
||||||
template <typename Pred>
|
template <typename Pred>
|
||||||
std::optional<Field> field_find_if(Pred&& pred) {
|
Option<Field> field_find_if(Pred&& pred) {
|
||||||
for (auto&& field: Fields)
|
for (auto&& field: Fields)
|
||||||
if (pred(field))
|
if (pred(field))
|
||||||
return field;
|
return field;
|
||||||
return std::nullopt;
|
return Nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -517,13 +517,13 @@ std::optional<Field> field_find_if(Pred&& pred) {
|
|||||||
* @return the message-field-id or nullopt.
|
* @return the message-field-id or nullopt.
|
||||||
*/
|
*/
|
||||||
static inline
|
static inline
|
||||||
std::optional<Field> field_from_shortcut(char shortcut) {
|
Option<Field> field_from_shortcut(char shortcut) {
|
||||||
return field_find_if([&](auto&& field){
|
return field_find_if([&](auto&& field){
|
||||||
return field.shortcut == shortcut;
|
return field.shortcut == shortcut;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static inline
|
static inline
|
||||||
std::optional<Field> field_from_name(const std::string& name) {
|
Option<Field> field_from_name(const std::string& name) {
|
||||||
if (name.length() == 1)
|
if (name.length() == 1)
|
||||||
return field_from_shortcut(name[0]);
|
return field_from_shortcut(name[0]);
|
||||||
else
|
else
|
||||||
@ -540,10 +540,10 @@ std::optional<Field> field_from_name(const std::string& name) {
|
|||||||
* @return Field::Id or nullopt
|
* @return Field::Id or nullopt
|
||||||
*/
|
*/
|
||||||
static inline
|
static inline
|
||||||
std::optional<Field> field_from_number(size_t id)
|
Option<Field> field_from_number(size_t id)
|
||||||
{
|
{
|
||||||
if (id >= static_cast<size_t>(Field::Id::_count_))
|
if (id >= static_cast<size_t>(Field::Id::_count_))
|
||||||
return std::nullopt;
|
return Nothing;
|
||||||
else
|
else
|
||||||
return field_from_id(static_cast<Field::Id>(id));
|
return field_from_id(static_cast<Field::Id>(id));
|
||||||
}
|
}
|
||||||
|
|||||||
85
lib/message/mu-message-part.cc
Normal file
85
lib/message/mu-message-part.cc
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2022 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
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "mu-message-part.hh"
|
||||||
|
#include "mu-mime-object.hh"
|
||||||
|
#include "mu-utils.hh"
|
||||||
|
|
||||||
|
using namespace Mu;
|
||||||
|
|
||||||
|
MessagePart::MessagePart(const Mu::MimeObject& obj):
|
||||||
|
mime_obj{std::make_unique<Mu::MimeObject>(obj)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
MessagePart::MessagePart(const MessagePart& other):
|
||||||
|
MessagePart(*other.mime_obj)
|
||||||
|
{}
|
||||||
|
|
||||||
|
MessagePart::~MessagePart() = default;
|
||||||
|
|
||||||
|
|
||||||
|
Option<std::string>
|
||||||
|
MessagePart::filename() const noexcept
|
||||||
|
{
|
||||||
|
if (!mime_obj->is_part())
|
||||||
|
return Nothing;
|
||||||
|
else
|
||||||
|
return MimePart(*mime_obj).filename();
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<std::string>
|
||||||
|
MessagePart::mime_type() const noexcept
|
||||||
|
{
|
||||||
|
if (const auto ctype{mime_obj->content_type()}; ctype)
|
||||||
|
return ctype->media_type() + "/" + ctype->media_subtype();
|
||||||
|
else
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
MessagePart::size() const noexcept
|
||||||
|
{
|
||||||
|
if (!mime_obj->is_part())
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return MimePart(*mime_obj).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Option<std::string>
|
||||||
|
MessagePart::to_string() const noexcept
|
||||||
|
{
|
||||||
|
if (mime_obj->is_part())
|
||||||
|
return MimePart(*mime_obj).to_string();
|
||||||
|
else
|
||||||
|
return mime_obj->object_to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Result<size_t>
|
||||||
|
MessagePart::to_file(const std::string& path, bool overwrite) const noexcept
|
||||||
|
{
|
||||||
|
if (!mime_obj->is_part())
|
||||||
|
return Err(Error::Code::InvalidArgument,
|
||||||
|
"not a part");
|
||||||
|
else
|
||||||
|
return MimePart(*mime_obj).to_file(path, overwrite);
|
||||||
|
}
|
||||||
101
lib/message/mu-message-part.hh
Normal file
101
lib/message/mu-message-part.hh
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2022 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
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef MU_MESSAGE_PART_HH__
|
||||||
|
#define MU_MESSAGE_PART_HH__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <utils/mu-option.hh>
|
||||||
|
#include <utils/mu-result.hh>
|
||||||
|
|
||||||
|
namespace Mu {
|
||||||
|
|
||||||
|
struct MimeObject; // forward declaration
|
||||||
|
|
||||||
|
class MessagePart {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct MessagePart from a MimeObject
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
*/
|
||||||
|
MessagePart(const MimeObject& obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy CTOR
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
|
MessagePart(const MessagePart& other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTOR
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
~MessagePart();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filename for the mime-part
|
||||||
|
*
|
||||||
|
* @return the filename or Nothing if there is none
|
||||||
|
*/
|
||||||
|
Option<std::string> filename() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mime-type for the mime-part (e.g. "text/plain")
|
||||||
|
*
|
||||||
|
* @return the mime-part or Nothing if there is none
|
||||||
|
*/
|
||||||
|
Option<std::string> mime_type() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the length of the (unencoded) MIME-part.
|
||||||
|
*
|
||||||
|
* @return the size
|
||||||
|
*/
|
||||||
|
size_t size() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write (decoded) mime-part contents to string
|
||||||
|
*
|
||||||
|
* @return a string or nothing if there is no contemt
|
||||||
|
*/
|
||||||
|
Option<std::string> to_string() const noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write (decoded) mime part to a file
|
||||||
|
*
|
||||||
|
* @param path path to file
|
||||||
|
* @param overwrite whether to possibly overwrite
|
||||||
|
*
|
||||||
|
* @return size of file or or an error.
|
||||||
|
*/
|
||||||
|
Result<size_t> to_file(const std::string& path, bool overwrite) const noexcept;
|
||||||
|
|
||||||
|
struct Private;
|
||||||
|
private:
|
||||||
|
std::unique_ptr<MimeObject> mime_obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Mu
|
||||||
|
|
||||||
|
#endif /* MU_MESSAGE_PART_HH__ */
|
||||||
File diff suppressed because it is too large
Load Diff
@ -23,19 +23,27 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "utils/mu-option.hh"
|
||||||
#include "mu-contact.hh"
|
#include "mu-contact.hh"
|
||||||
#include "mu-priority.hh"
|
#include "mu-priority.hh"
|
||||||
#include "mu-flags.hh"
|
#include "mu-flags.hh"
|
||||||
#include "mu-fields.hh"
|
#include "mu-fields.hh"
|
||||||
#include "mu-document.hh"
|
#include "mu-document.hh"
|
||||||
|
#include "mu-message-part.hh"
|
||||||
|
|
||||||
struct _GMimeMessage;
|
#include "utils/mu-result.hh"
|
||||||
|
|
||||||
namespace Mu {
|
namespace Mu {
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Move CTOR
|
||||||
|
*
|
||||||
|
* @param some other message
|
||||||
|
*/
|
||||||
|
Message(Message&& msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a message based on a path
|
* Construct a message based on a path
|
||||||
*
|
*
|
||||||
@ -44,57 +52,52 @@ public:
|
|||||||
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar; you can
|
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar; you can
|
||||||
* pass NULL for this parameter, in which case some maildir-specific
|
* pass NULL for this parameter, in which case some maildir-specific
|
||||||
* information is not available.
|
* information is not available.
|
||||||
|
*
|
||||||
|
* @return a message or an error
|
||||||
*/
|
*/
|
||||||
Message(const std::string& path, const std::string& mdir);
|
static Result<Message> make_from_path(const std::string& path, const std::string& mdir) try {
|
||||||
|
return Ok(Message{path, mdir});
|
||||||
|
} catch (Error& err) {
|
||||||
|
return Err(err);
|
||||||
|
} catch (...) {
|
||||||
|
return Err(Mu::Error(Error::Code::Message, "failed to create message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a message based on a Message::Document
|
* Construct a message based on a Message::Document
|
||||||
*
|
*
|
||||||
* @param doc
|
* @param doc
|
||||||
*/
|
|
||||||
Message(Document& doc): doc_{doc} {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy CTOR
|
|
||||||
*
|
*
|
||||||
* @param rhs a Message
|
* @return a message or an error
|
||||||
*/
|
*/
|
||||||
Message(const Message& rhs) {
|
static Result<Message> make_from_document(Document& doc) try {
|
||||||
*this = rhs;
|
return Ok(Message{doc});
|
||||||
|
} catch (Error& err) {
|
||||||
|
return Err(err);
|
||||||
|
} catch (...) {
|
||||||
|
return Err(Mu::Error(Error::Code::Message, "failed to create message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move CTOR
|
* Construct a message from a string. This is mostly useful for testing.
|
||||||
*
|
*
|
||||||
* @param rhs a Message
|
* @param text message text
|
||||||
|
*
|
||||||
|
* @return a message or an error
|
||||||
*/
|
*/
|
||||||
|
static Result<Message> make_from_string(const std::string& text) try {
|
||||||
Message(Message&& rhs) {
|
return Ok(Message{text});
|
||||||
*this = std::move(rhs);
|
} catch (Error& err) {
|
||||||
|
return Err(err);
|
||||||
|
} catch (...) {
|
||||||
|
return Err(Mu::Error(Error::Code::Message, "failed to create message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTOR
|
* DTOR
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
~Message();
|
~Message();
|
||||||
/**
|
|
||||||
* Copy assignment operator
|
|
||||||
*
|
|
||||||
* @param rhs some message
|
|
||||||
*
|
|
||||||
* @return a message ref
|
|
||||||
*/
|
|
||||||
Message& operator=(const Message& rhs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move assignment operator
|
|
||||||
*
|
|
||||||
* @param rhs some message
|
|
||||||
*
|
|
||||||
* @return a message ref
|
|
||||||
*/
|
|
||||||
Message& operator=(Message&& rhs);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the document.
|
* Get the document.
|
||||||
@ -102,7 +105,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @return document
|
* @return document
|
||||||
*/
|
*/
|
||||||
const Document& document() const { return doc_; }
|
const Document& document() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the file system path of this message
|
* Get the file system path of this message
|
||||||
@ -110,50 +113,50 @@ public:
|
|||||||
* @return the path of this Message or NULL in case of error.
|
* @return the path of this Message or NULL in case of error.
|
||||||
* the returned string should *not* be modified or freed.
|
* the returned string should *not* be modified or freed.
|
||||||
*/
|
*/
|
||||||
std::string path() const { return doc_.string_value(Field::Id::Path); }
|
std::string path() const { return document().string_value(Field::Id::Path); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the sender (From:) of this message
|
* Get the sender (From:) of this message
|
||||||
*
|
*
|
||||||
* @return the sender(s) of this Message
|
* @return the sender(s) of this Message
|
||||||
*/
|
*/
|
||||||
Contacts from() const { return doc_.contacts_value(Field::Id::From); }
|
Contacts from() const { return document().contacts_value(Field::Id::From); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the recipient(s) (To:) for this message
|
* Get the recipient(s) (To:) for this message
|
||||||
*
|
*
|
||||||
* @return recipients
|
* @return recipients
|
||||||
*/
|
*/
|
||||||
Contacts to() const { return doc_.contacts_value(Field::Id::To); }
|
Contacts to() const { return document().contacts_value(Field::Id::To); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the recipient(s) (Cc:) for this message
|
* Get the recipient(s) (Cc:) for this message
|
||||||
*
|
*
|
||||||
* @return recipients
|
* @return recipients
|
||||||
*/
|
*/
|
||||||
Contacts cc() const { return doc_.contacts_value(Field::Id::Cc); }
|
Contacts cc() const { return document().contacts_value(Field::Id::Cc); }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the recipient(s) (Bcc:) for this message
|
* Get the recipient(s) (Bcc:) for this message
|
||||||
*
|
*
|
||||||
* @return recipients
|
* @return recipients
|
||||||
*/
|
*/
|
||||||
Contacts bcc() const { return doc_.contacts_value(Field::Id::Bcc); }
|
Contacts bcc() const { return document().contacts_value(Field::Id::Bcc); }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the maildir this message lives in; ie, if the path is
|
* Get the maildir this message lives in; ie, if the path is
|
||||||
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar
|
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar
|
||||||
*
|
*
|
||||||
* @return the maildir requested or empty */
|
* @return the maildir requested or empty */
|
||||||
std::string maildir() const { return doc_.string_value(Field::Id::Maildir); }
|
std::string maildir() const { return document().string_value(Field::Id::Maildir); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the subject of this message
|
* Get the subject of this message
|
||||||
*
|
*
|
||||||
* @return the subject of this Message
|
* @return the subject of this Message
|
||||||
*/
|
*/
|
||||||
std::string subject() const { return doc_.string_value(Field::Id::Subject); }
|
std::string subject() const { return document().string_value(Field::Id::Subject); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Message-Id of this message
|
* Get the Message-Id of this message
|
||||||
@ -161,7 +164,7 @@ public:
|
|||||||
* @return the Message-Id of this message (without the enclosing <>), or
|
* @return the Message-Id of this message (without the enclosing <>), or
|
||||||
* a fake message-id for messages that don't have them
|
* a fake message-id for messages that don't have them
|
||||||
*/
|
*/
|
||||||
std::string message_id() const { return doc_.string_value(Field::Id::MessageId);}
|
std::string message_id() const { return document().string_value(Field::Id::MessageId);}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the mailing list for a message, i.e. the mailing-list
|
* get the mailing list for a message, i.e. the mailing-list
|
||||||
@ -170,7 +173,7 @@ public:
|
|||||||
* @return the mailing list id for this message (without the enclosing <>)
|
* @return the mailing list id for this message (without the enclosing <>)
|
||||||
* or NULL in case of error or if there is none.
|
* or NULL in case of error or if there is none.
|
||||||
*/
|
*/
|
||||||
std::string mailing_list() const { return doc_.string_value(Field::Id::MailingList);}
|
std::string mailing_list() const { return document().string_value(Field::Id::MailingList);}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the message date/time (the Date: field) as time_t, using UTC
|
* get the message date/time (the Date: field) as time_t, using UTC
|
||||||
@ -178,14 +181,14 @@ public:
|
|||||||
* @return message date/time or 0 in case of error or if there
|
* @return message date/time or 0 in case of error or if there
|
||||||
* is no such header.
|
* is no such header.
|
||||||
*/
|
*/
|
||||||
time_t date() const { return static_cast<time_t>(doc_.integer_value(Field::Id::Date)); }
|
::time_t date() const { return static_cast<time_t>(document().integer_value(Field::Id::Date)); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the flags for this message
|
* get the flags for this message
|
||||||
*
|
*
|
||||||
* @return the file/content flags
|
* @return the file/content flags
|
||||||
*/
|
*/
|
||||||
Flags flags() const { return doc_.flags_value(); }
|
Flags flags() const { return document().flags_value(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the message priority for this message. The X-Priority, X-MSMailPriority,
|
* get the message priority for this message. The X-Priority, X-MSMailPriority,
|
||||||
@ -194,14 +197,14 @@ public:
|
|||||||
*
|
*
|
||||||
* @return the message priority
|
* @return the message priority
|
||||||
*/
|
*/
|
||||||
Priority priority() const { return doc_.priority_value(); }
|
Priority priority() const { return document().priority_value(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the file size in bytes of this message
|
* get the file size in bytes of this message
|
||||||
*
|
*
|
||||||
* @return the filesize
|
* @return the filesize
|
||||||
*/
|
*/
|
||||||
size_t size() const { return static_cast<size_t>(doc_.integer_value(Field::Id::Size)); }
|
size_t size() const { return static_cast<size_t>(document().integer_value(Field::Id::Size)); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the list of references (consisting of both the References and
|
* get the list of references (consisting of both the References and
|
||||||
@ -212,7 +215,7 @@ public:
|
|||||||
* @return a vec with the references for this msg.
|
* @return a vec with the references for this msg.
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> references() const {
|
std::vector<std::string> references() const {
|
||||||
return doc_.string_vec_value(Field::Id::References);
|
return document().string_vec_value(Field::Id::References);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,22 +226,82 @@ public:
|
|||||||
* @return a list with the tags for this msg. Don't modify/free
|
* @return a list with the tags for this msg. Don't modify/free
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> tags() const {
|
std::vector<std::string> tags() const {
|
||||||
return doc_.string_vec_value(Field::Id::References);
|
return document().string_vec_value(Field::Id::References);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Below require a file-backed message, which is a relatively slow
|
||||||
|
* if there isn't one already ()
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text body
|
||||||
|
*
|
||||||
|
* @return text body
|
||||||
|
*/
|
||||||
|
Option<std::string> body_text() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the HTML body
|
||||||
|
*
|
||||||
|
* @return text body
|
||||||
|
*/
|
||||||
|
Option<std::string> body_html() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get some message-header
|
* Get some message-header
|
||||||
*
|
*
|
||||||
* @param header_field name of the header
|
* @param header_field name of the header
|
||||||
*
|
*
|
||||||
* @return the value
|
* @return the value (UTF-8), or Nothing.
|
||||||
*/
|
*/
|
||||||
std::string header(const std::string& header_field) const;
|
Option<std::string> header(const std::string& header_field) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all contacts for this message.
|
||||||
|
*
|
||||||
|
* @return contacts
|
||||||
|
*/
|
||||||
|
Contacts all_contacts() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about MIME-parts in this message.
|
||||||
|
*
|
||||||
|
* @return mime-part info.
|
||||||
|
*/
|
||||||
|
using Part = MessagePart;
|
||||||
|
const std::vector<Part>& parts() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the GMime (file) message (for a database-backed message),
|
||||||
|
* if not already (but see @param reload).
|
||||||
|
*
|
||||||
|
* Affects cached-state only, so we still mark this as 'const'
|
||||||
|
*
|
||||||
|
* @param reload whether to force reloading (even if already)
|
||||||
|
*
|
||||||
|
* @return true if loading worked; false otherwise.
|
||||||
|
*/
|
||||||
|
bool load_mime_message(bool reload=false) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the GMime message.
|
||||||
|
*
|
||||||
|
* Affects cached-state only, so we still mark this as 'const'
|
||||||
|
*/
|
||||||
|
void unload_mime_message() const;
|
||||||
|
|
||||||
|
struct Private;
|
||||||
private:
|
private:
|
||||||
Document doc_;
|
Message(const std::string& path, const std::string& mdir);
|
||||||
mutable struct _GMimeMessage *mime_msg_{};
|
Message(const std::string& str);
|
||||||
|
Message(Document& doc);
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<Private> priv_;
|
||||||
}; // Message
|
}; // Message
|
||||||
} // Mu
|
} // Mu
|
||||||
#endif /* MU_MESSAGE_HH__ */
|
#endif /* MU_MESSAGE_HH__ */
|
||||||
|
|||||||
363
lib/message/mu-mime-object.cc
Normal file
363
lib/message/mu-mime-object.cc
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2022 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
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "mu-mime-object.hh"
|
||||||
|
#include "gmime/gmime-message.h"
|
||||||
|
#include "mu-utils.hh"
|
||||||
|
#include <mutex>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace Mu;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* note, we do the gmime initialization here rather than in mu-runtime, because this way
|
||||||
|
* we don't need mu-runtime for simple cases -- such as our unit tests. Also note that we
|
||||||
|
* need gmime init even for the doc backend, as we use the address parsing functions also
|
||||||
|
* there. */
|
||||||
|
|
||||||
|
void
|
||||||
|
Mu::init_gmime(void)
|
||||||
|
{
|
||||||
|
// fast path.
|
||||||
|
static bool gmime_initialized = false;
|
||||||
|
if (gmime_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::mutex gmime_lock;
|
||||||
|
std::lock_guard lock (gmime_lock);
|
||||||
|
if (gmime_initialized)
|
||||||
|
return; // already
|
||||||
|
|
||||||
|
g_debug("initializing gmime %u.%u.%u",
|
||||||
|
gmime_major_version,
|
||||||
|
gmime_minor_version,
|
||||||
|
gmime_micro_version);
|
||||||
|
|
||||||
|
g_mime_init();
|
||||||
|
gmime_initialized = true;
|
||||||
|
|
||||||
|
std::atexit([] {
|
||||||
|
g_debug("shutting down gmime");
|
||||||
|
g_mime_shutdown();
|
||||||
|
gmime_initialized = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MimeObject
|
||||||
|
*/
|
||||||
|
|
||||||
|
Option<std::string>
|
||||||
|
MimeObject::header(const std::string& hdr) const noexcept
|
||||||
|
{
|
||||||
|
const char *val{g_mime_object_get_header(self(), hdr.c_str())};
|
||||||
|
if (!val)
|
||||||
|
return Nothing;
|
||||||
|
if (!g_utf8_validate(val, -1, {}))
|
||||||
|
return utf8_clean(hdr);
|
||||||
|
else
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Option<std::string>
|
||||||
|
MimeObject::object_to_string() const noexcept
|
||||||
|
{
|
||||||
|
GMimeStream *stream{g_mime_stream_mem_new()};
|
||||||
|
if (!stream) {
|
||||||
|
g_warning("failed to create mem stream");
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto written = g_mime_object_write_to_stream(self(), {}, stream);
|
||||||
|
if (written < 0) {
|
||||||
|
g_warning("failed to write object to stream");
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string buffer;
|
||||||
|
buffer.resize(written + 1);
|
||||||
|
g_mime_stream_reset(stream);
|
||||||
|
|
||||||
|
auto bytes{g_mime_stream_read(stream, buffer.data(), written)};
|
||||||
|
g_object_unref(stream);
|
||||||
|
if (bytes < 0)
|
||||||
|
return Nothing;
|
||||||
|
|
||||||
|
buffer.data()[written]='\0';
|
||||||
|
buffer.resize(written);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MimeMessage
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static Result<MimeMessage>
|
||||||
|
make_from_stream(GMimeStream* &&stream/*consume*/)
|
||||||
|
{
|
||||||
|
GMimeParser *parser{g_mime_parser_new_with_stream(stream)};
|
||||||
|
g_object_unref(stream);
|
||||||
|
if (!parser)
|
||||||
|
return Err(Error::Code::Message, "cannot create mime parser");
|
||||||
|
|
||||||
|
GMimeMessage *gmime_msg{g_mime_parser_construct_message(parser, NULL)};
|
||||||
|
g_object_unref(parser);
|
||||||
|
if (!gmime_msg)
|
||||||
|
return Err(Error::Code::Message, "message seems invalid");
|
||||||
|
|
||||||
|
auto mime_msg{MimeMessage{std::move(G_OBJECT(gmime_msg))}};
|
||||||
|
g_object_unref(gmime_msg);
|
||||||
|
|
||||||
|
return Ok(std::move(mime_msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<MimeMessage>
|
||||||
|
MimeMessage::make_from_file(const std::string& path)
|
||||||
|
{
|
||||||
|
GError* err{};
|
||||||
|
if (auto&& stream{g_mime_stream_file_open(path.c_str(), "r", &err)}; !stream)
|
||||||
|
return Err(Error::Code::Message, &err,
|
||||||
|
"failed to open stream for %s", path.c_str());
|
||||||
|
else
|
||||||
|
return make_from_stream(std::move(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<MimeMessage>
|
||||||
|
MimeMessage::make_from_string(const std::string& text)
|
||||||
|
{
|
||||||
|
if (auto&& stream{g_mime_stream_mem_new_with_buffer(
|
||||||
|
text.c_str(), text.length())}; !stream)
|
||||||
|
return Err(Error::Code::Message,
|
||||||
|
"failed to open stream for string");
|
||||||
|
else
|
||||||
|
return make_from_stream(std::move(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<int64_t>
|
||||||
|
MimeMessage::date() const noexcept
|
||||||
|
{
|
||||||
|
GDateTime *dt{g_mime_message_get_date(self())};
|
||||||
|
if (!dt)
|
||||||
|
return Nothing;
|
||||||
|
else
|
||||||
|
return g_date_time_to_unix(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mu::Contacts
|
||||||
|
MimeMessage::addresses(AddressType atype) const noexcept
|
||||||
|
{
|
||||||
|
auto addrs{g_mime_message_get_addresses(
|
||||||
|
self(), static_cast<GMimeAddressType>(atype))};
|
||||||
|
if (!addrs)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
|
||||||
|
const auto msgtime{date().value_or(0)};
|
||||||
|
const auto opt_field_id = std::invoke(
|
||||||
|
[&]()->Option<Field::Id>{
|
||||||
|
switch(atype) {
|
||||||
|
case AddressType::To:
|
||||||
|
return Field::Id::To;
|
||||||
|
case AddressType::From:
|
||||||
|
return Field::Id::From;
|
||||||
|
case AddressType::Bcc:
|
||||||
|
return Field::Id::Bcc;
|
||||||
|
case AddressType::Cc:
|
||||||
|
return Field::Id::Cc;
|
||||||
|
default:
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Contacts contacts;
|
||||||
|
auto lst_len{internet_address_list_length(addrs)};
|
||||||
|
contacts.reserve(lst_len);
|
||||||
|
for (auto i = 0; i != lst_len; ++i) {
|
||||||
|
|
||||||
|
auto&& addr{internet_address_list_get_address(addrs, i)};
|
||||||
|
const auto name{internet_address_get_name(addr)};
|
||||||
|
|
||||||
|
if (G_UNLIKELY(!INTERNET_ADDRESS_IS_MAILBOX(addr)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto email{internet_address_mailbox_get_addr (
|
||||||
|
INTERNET_ADDRESS_MAILBOX(addr))};
|
||||||
|
if (G_UNLIKELY(!email))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
contacts.push_back(Contact{email, name ? name : "",
|
||||||
|
opt_field_id, msgtime});
|
||||||
|
}
|
||||||
|
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string>
|
||||||
|
MimeMessage::references() const noexcept
|
||||||
|
{
|
||||||
|
constexpr std::array<const char*, 2> ref_headers = {
|
||||||
|
"References", "In-reply-to",
|
||||||
|
};
|
||||||
|
|
||||||
|
// is ref already in the list?
|
||||||
|
auto is_dup = [](auto&& seq, const std::string& ref) {
|
||||||
|
return seq_find_if(seq, [&](auto&& str) { return ref == str; })
|
||||||
|
== seq.cend();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> refs;
|
||||||
|
for (auto&& ref_header: ref_headers) {
|
||||||
|
|
||||||
|
auto hdr{header(ref_header)};
|
||||||
|
if (!hdr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GMimeReferences *mime_refs{g_mime_references_parse({}, hdr->c_str())};
|
||||||
|
refs.reserve(refs.size() + g_mime_references_length(mime_refs));
|
||||||
|
|
||||||
|
for (auto i = 0; i != g_mime_references_length(mime_refs); ++i) {
|
||||||
|
|
||||||
|
if (auto&& msgid{g_mime_references_get_message_id(mime_refs, i)}; !msgid)
|
||||||
|
continue; // invalid
|
||||||
|
else if (is_dup(refs, msgid))
|
||||||
|
continue; // skip dups
|
||||||
|
else
|
||||||
|
refs.emplace_back(msgid);
|
||||||
|
}
|
||||||
|
g_mime_references_free(mime_refs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
MimeMessage::for_each(const ForEachFunc& func) const noexcept
|
||||||
|
{
|
||||||
|
struct CallbackData { const ForEachFunc& func; };
|
||||||
|
CallbackData cbd{func};
|
||||||
|
|
||||||
|
g_mime_message_foreach(
|
||||||
|
self(),
|
||||||
|
[] (GMimeObject *parent, GMimeObject *part, gpointer user_data) {
|
||||||
|
auto cbd{reinterpret_cast<CallbackData*>(user_data)};
|
||||||
|
cbd->func(MimeObject{parent}, MimeObject{part});
|
||||||
|
}, &cbd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MimePart
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
MimePart::size() const noexcept
|
||||||
|
{
|
||||||
|
auto wrapper{g_mime_part_get_content(self())};
|
||||||
|
if (!wrapper) {
|
||||||
|
g_warning("failed to get content wrapper");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stream{g_mime_data_wrapper_get_stream(wrapper)};
|
||||||
|
if (!stream) {
|
||||||
|
g_warning("failed to get stream");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<size_t>(g_mime_stream_length(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<std::string>
|
||||||
|
MimePart::to_string() const noexcept
|
||||||
|
{
|
||||||
|
GMimeDataWrapper *wrapper{g_mime_part_get_content(self())};
|
||||||
|
if (!wrapper) { /* this happens with invalid mails */
|
||||||
|
g_debug("failed to create data wrapper");
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
GMimeStream *stream{g_mime_stream_mem_new()};
|
||||||
|
if (!stream) {
|
||||||
|
g_warning("failed to create mem stream");
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ssize_t buflen{g_mime_data_wrapper_write_to_stream(wrapper, stream)};
|
||||||
|
if (buflen <= 0) { /* empty buffer, not an error */
|
||||||
|
g_object_unref(stream);
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string buffer;
|
||||||
|
buffer.resize(buflen + 1);
|
||||||
|
g_mime_stream_reset(stream);
|
||||||
|
|
||||||
|
auto bytes{g_mime_stream_read(stream, buffer.data(), buflen)};
|
||||||
|
g_object_unref(stream);
|
||||||
|
if (bytes < 0)
|
||||||
|
return Nothing;
|
||||||
|
|
||||||
|
buffer.data()[bytes]='\0';
|
||||||
|
buffer.resize(buflen);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Result<size_t>
|
||||||
|
MimePart::to_file(const std::string& path, bool overwrite) const noexcept
|
||||||
|
{
|
||||||
|
GMimeDataWrapper *wrapper{g_mime_part_get_content(self())};
|
||||||
|
if (!wrapper) /* this happens with invalid mails */
|
||||||
|
return Err(Error::Code::File, "failed to create data wrapper");
|
||||||
|
|
||||||
|
|
||||||
|
GError *err{};
|
||||||
|
GMimeStream *stream{g_mime_stream_fs_open(
|
||||||
|
path.c_str(),
|
||||||
|
O_WRONLY | O_CREAT | O_TRUNC |(overwrite ? 0 : O_EXCL),
|
||||||
|
S_IRUSR|S_IWUSR,
|
||||||
|
&err)};
|
||||||
|
if (!stream)
|
||||||
|
return Err(Error::Code::File, &err,
|
||||||
|
"failed to open '%s'", path.c_str());
|
||||||
|
|
||||||
|
ssize_t written{g_mime_data_wrapper_write_to_stream(wrapper, stream)};
|
||||||
|
g_object_unref(stream);
|
||||||
|
if (written < 0) {
|
||||||
|
return Err(Error::Code::File, &err,
|
||||||
|
"failed to write to '%s'", path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(static_cast<size_t>(written));
|
||||||
|
}
|
||||||
665
lib/message/mu-mime-object.hh
Normal file
665
lib/message/mu-mime-object.hh
Normal file
@ -0,0 +1,665 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2022 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
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MU_MIME_OBJECT_HH__
|
||||||
|
#define MU_MIME_OBJECT_HH__
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <gmime/gmime.h>
|
||||||
|
#include "gmime/gmime-application-pkcs7-mime.h"
|
||||||
|
#include "utils/mu-option.hh"
|
||||||
|
#include "utils/mu-result.hh"
|
||||||
|
#include "mu-contact.hh"
|
||||||
|
|
||||||
|
namespace Mu {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize gmime (idempotent)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void init_gmime(void);
|
||||||
|
|
||||||
|
class Object {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Default CTOR
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Object() noexcept: self_{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an object from a GObject
|
||||||
|
*
|
||||||
|
* @param obj a gobject. A ref is added.
|
||||||
|
*/
|
||||||
|
Object(GObject* &&obj): self_{g_object_ref(obj)} {
|
||||||
|
if (!G_IS_OBJECT(obj))
|
||||||
|
throw std::runtime_error("not a g-object");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy CTOR
|
||||||
|
*
|
||||||
|
* @param other some other Object
|
||||||
|
*/
|
||||||
|
Object(const Object& other) noexcept { *this = other; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move CTOR
|
||||||
|
*
|
||||||
|
* @param other some other Object
|
||||||
|
*/
|
||||||
|
Object(Object&& other) noexcept { *this = std::move(other); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* operator=
|
||||||
|
*
|
||||||
|
* @param other copy some other object
|
||||||
|
*
|
||||||
|
* @return *this
|
||||||
|
*/
|
||||||
|
Object& operator=(const Object& other) noexcept {
|
||||||
|
|
||||||
|
if (this != &other) {
|
||||||
|
auto oldself = self_;
|
||||||
|
self_ = other.self_ ? g_object_ref(other.self_) : nullptr;
|
||||||
|
if (oldself)
|
||||||
|
g_object_unref(oldself);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* operator=
|
||||||
|
*
|
||||||
|
* @param other move some object object
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Object& operator=(Object&& other) noexcept {
|
||||||
|
|
||||||
|
if (this != &other) {
|
||||||
|
auto oldself = self_;
|
||||||
|
self_ = other.self_;
|
||||||
|
other.self_ = nullptr;
|
||||||
|
if (oldself)
|
||||||
|
g_object_unref(oldself);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTOR
|
||||||
|
*/
|
||||||
|
virtual ~Object() {
|
||||||
|
if (self_) {
|
||||||
|
g_object_unref(self_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* operator bool
|
||||||
|
*
|
||||||
|
* @return true if object wraps a GObject, false otherwise
|
||||||
|
*/
|
||||||
|
operator bool() const noexcept { return !!self_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
GObject* object() const { return self(); }
|
||||||
|
|
||||||
|
static Option<std::string> maybe_string(const char *str) noexcept {
|
||||||
|
if (!str)
|
||||||
|
return Nothing;
|
||||||
|
else
|
||||||
|
return std::string(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GObject *self() const { return self_; }
|
||||||
|
mutable GObject *self_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct MimeContentType: public Object {
|
||||||
|
|
||||||
|
MimeContentType(GMimeContentType *ctype) : Object{G_OBJECT(ctype)} {
|
||||||
|
if (!GMIME_IS_CONTENT_TYPE(self()))
|
||||||
|
throw std::runtime_error("not a content-type");
|
||||||
|
}
|
||||||
|
std::string media_type() const {
|
||||||
|
return g_mime_content_type_get_media_type(self());
|
||||||
|
}
|
||||||
|
std::string media_subtype() const {
|
||||||
|
return g_mime_content_type_get_media_subtype(self());
|
||||||
|
}
|
||||||
|
bool is_type(const std::string& type, const std::string& subtype) const {
|
||||||
|
return g_mime_content_type_is_type(self(), type.c_str(),
|
||||||
|
subtype.c_str());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
GMimeContentType* self() const {
|
||||||
|
return reinterpret_cast<GMimeContentType*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimeObject
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimeObject: public Object {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a new MimeObject. Take a ref on the obj
|
||||||
|
*
|
||||||
|
* @param mime_part mime-part pointer
|
||||||
|
*/
|
||||||
|
MimeObject(const Object& obj): Object{obj} {
|
||||||
|
if (!GMIME_IS_OBJECT(self()))
|
||||||
|
throw std::runtime_error("not a mime-object");
|
||||||
|
}
|
||||||
|
MimeObject(GMimeObject *mobj): Object{G_OBJECT(mobj)} {
|
||||||
|
if (!GMIME_IS_OBJECT(self()))
|
||||||
|
throw std::runtime_error("not a mime-object");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a header from the MimeObject
|
||||||
|
*
|
||||||
|
* @param header the header to retrieve
|
||||||
|
*
|
||||||
|
* @return header value (UTF-8) or Nothing
|
||||||
|
*/
|
||||||
|
Option<std::string> header(const std::string& header) const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content type
|
||||||
|
*
|
||||||
|
* @return the content-type or Nothing
|
||||||
|
*/
|
||||||
|
Option<MimeContentType> content_type() const noexcept {
|
||||||
|
auto ct{g_mime_object_get_content_type(self())};
|
||||||
|
if (!ct)
|
||||||
|
return Nothing;
|
||||||
|
else
|
||||||
|
return MimeContentType(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the object to a string.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Option<std::string> object_to_string() const noexcept;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* subtypes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimePart?
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool is_part() const { return GMIME_IS_PART(self()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimeMultiPart?
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool is_multipart() const { return GMIME_IS_MULTIPART(self());}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimeMultiPart?
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool is_multipart_encrypted() const {
|
||||||
|
return GMIME_IS_MULTIPART_ENCRYPTED(self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimeMultiPart?
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool is_multipart_signed() const {
|
||||||
|
return GMIME_IS_MULTIPART_SIGNED(self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimeMessage?
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool is_message() const { return GMIME_IS_MESSAGE(self());}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimeMessagePart?
|
||||||
|
*
|
||||||
|
* @return true orf alse
|
||||||
|
*/
|
||||||
|
bool is_message_part() const { return GMIME_IS_MESSAGE_PART(self());}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a MimeApplicationpkcs7Mime?
|
||||||
|
*
|
||||||
|
* @return true orf alse
|
||||||
|
*/
|
||||||
|
bool is_mime_application_pkcs7_mime() const {
|
||||||
|
return GMIME_IS_APPLICATION_PKCS7_MIME(self());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimeObject* self() const {
|
||||||
|
return reinterpret_cast<GMimeObject*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimeMessage
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimeMessage: public MimeObject {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a MimeMessage
|
||||||
|
*
|
||||||
|
* @param obj an Object of the right type
|
||||||
|
*/
|
||||||
|
MimeMessage(const Object& obj): MimeObject(obj) {
|
||||||
|
if (!is_message())
|
||||||
|
throw std::runtime_error("not a mime-message");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a MimeMessage from a file
|
||||||
|
*
|
||||||
|
* @param path path to the file
|
||||||
|
*
|
||||||
|
* @return a MimeMessage or an error.
|
||||||
|
*/
|
||||||
|
static Result<MimeMessage> make_from_file (const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a MimeMessage from a string
|
||||||
|
*
|
||||||
|
* @param path path to the file
|
||||||
|
*
|
||||||
|
* @return a MimeMessage or an error.
|
||||||
|
*/
|
||||||
|
static Result<MimeMessage> make_from_string (const std::string& text);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address types
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
enum struct AddressType {
|
||||||
|
Sender = GMIME_ADDRESS_TYPE_SENDER,
|
||||||
|
From = GMIME_ADDRESS_TYPE_FROM,
|
||||||
|
ReplyTo = GMIME_ADDRESS_TYPE_REPLY_TO,
|
||||||
|
To = GMIME_ADDRESS_TYPE_TO,
|
||||||
|
Cc = GMIME_ADDRESS_TYPE_CC,
|
||||||
|
Bcc = GMIME_ADDRESS_TYPE_BCC
|
||||||
|
};
|
||||||
|
|
||||||
|
Contacts addresses(AddressType atype) const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the message-id if it exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> message_id() const noexcept {
|
||||||
|
return maybe_string(g_mime_message_get_message_id(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the message-id if it exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> subject() const noexcept {
|
||||||
|
return maybe_string(g_mime_message_get_subject(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the date if it exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return a time_t value (expressed as a 64-bit number) or nullopt
|
||||||
|
*/
|
||||||
|
Option<int64_t> date() const noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the references for this message (including in-reply-to), in the
|
||||||
|
* order of older..newer; in-reply-to would be the last one.
|
||||||
|
*
|
||||||
|
* @return references.
|
||||||
|
*/
|
||||||
|
std::vector<std::string> references() const noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for for_each(). See GMimeObjectForEachFunc.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
using ForEachFunc = std::function<void(const MimeObject& parent,
|
||||||
|
const MimeObject& part)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively apply func tol all parts of this message
|
||||||
|
*
|
||||||
|
* @param func a function
|
||||||
|
*/
|
||||||
|
void for_each(const ForEachFunc& func) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimeMessage* self() const {
|
||||||
|
return reinterpret_cast<GMimeMessage*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimePart.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimePart: public MimeObject {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a MimePart
|
||||||
|
*
|
||||||
|
* @param obj an Object of the right type
|
||||||
|
*/
|
||||||
|
MimePart(const Object& obj): MimeObject(obj) {
|
||||||
|
if (!is_part())
|
||||||
|
throw std::runtime_error("not a mime-part");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the part is an attachment based on the
|
||||||
|
* value of the Content-Disposition header.
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool is_attachment() const noexcept {
|
||||||
|
return g_mime_part_is_attachment(self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the Content-Description for this mime part
|
||||||
|
* if it exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> content_description() const noexcept {
|
||||||
|
return maybe_string(g_mime_part_get_content_description(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the Content-Id for this mime part
|
||||||
|
* if it exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> content_id() const noexcept {
|
||||||
|
return maybe_string(g_mime_part_get_content_id(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the Content-Md5 header for this mime part
|
||||||
|
* if it exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> content_md5() const noexcept {
|
||||||
|
return maybe_string(g_mime_part_get_content_md5(self()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the content md5 for the specified mime part. Returns false if
|
||||||
|
* the mime part does not contain a Content-MD5.
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool verify_content_md5() const noexcept {
|
||||||
|
return g_mime_part_verify_content_md5(self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the Content-Location for this mime part if it
|
||||||
|
* exists, or nullopt otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> content_location() const noexcept {
|
||||||
|
return maybe_string(g_mime_part_get_content_location(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the filename for this mime part if it exists, or nullopt
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @return string or nullopt
|
||||||
|
*/
|
||||||
|
Option<std::string> filename() const noexcept {
|
||||||
|
return maybe_string(g_mime_part_get_filename(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of content, in bytes
|
||||||
|
*
|
||||||
|
* @return size
|
||||||
|
*/
|
||||||
|
size_t size() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get as UTF-8 string
|
||||||
|
*
|
||||||
|
* @return a string, or NULL.
|
||||||
|
*/
|
||||||
|
Option<std::string> to_string() const noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write part to a file
|
||||||
|
*
|
||||||
|
* @param path path to file
|
||||||
|
* @param overwrite if true, overwrite existing file, if it bqexists
|
||||||
|
*
|
||||||
|
* @return size of the wrtten file, or an error.
|
||||||
|
*/
|
||||||
|
Result<size_t> to_file(const std::string& path, bool overwrite)
|
||||||
|
const noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of Content Encoding.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
enum struct ContentEncoding {
|
||||||
|
Default = GMIME_CONTENT_ENCODING_DEFAULT,
|
||||||
|
SevenBit = GMIME_CONTENT_ENCODING_7BIT,
|
||||||
|
EightBit = GMIME_CONTENT_ENCODING_8BIT,
|
||||||
|
Binary = GMIME_CONTENT_ENCODING_BINARY,
|
||||||
|
Base64 = GMIME_CONTENT_ENCODING_BASE64,
|
||||||
|
QuotedPrintable = GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE,
|
||||||
|
UuEncode = GMIME_CONTENT_ENCODING_UUENCODE
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the content encoding of the mime part.
|
||||||
|
*
|
||||||
|
* @return the content encoding
|
||||||
|
*/
|
||||||
|
ContentEncoding content_encoding() const noexcept {
|
||||||
|
const auto enc{g_mime_part_get_content_encoding(self())};
|
||||||
|
g_return_val_if_fail(enc <= GMIME_CONTENT_ENCODING_UUENCODE,
|
||||||
|
ContentEncoding::Default);
|
||||||
|
return static_cast<ContentEncoding>(enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of OpenPGP data
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
enum struct OpenPGPData {
|
||||||
|
None = GMIME_OPENPGP_DATA_NONE,
|
||||||
|
Encrypted = GMIME_OPENPGP_DATA_ENCRYPTED,
|
||||||
|
Signed = GMIME_OPENPGP_DATA_SIGNED,
|
||||||
|
PublicKey = GMIME_OPENPGP_DATA_PUBLIC_KEY,
|
||||||
|
PrivateKey = GMIME_OPENPGP_DATA_PRIVATE_KEY,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether or not (and what type) of OpenPGP data is contained
|
||||||
|
*
|
||||||
|
* @return OpenGPGData
|
||||||
|
*/
|
||||||
|
OpenPGPData openpgp_data() const noexcept {
|
||||||
|
const auto data{g_mime_part_get_openpgp_data(self())};
|
||||||
|
g_return_val_if_fail(data <= GMIME_OPENPGP_DATA_PRIVATE_KEY,
|
||||||
|
OpenPGPData::None);
|
||||||
|
return static_cast<OpenPGPData>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimePart* self() const {
|
||||||
|
return reinterpret_cast<GMimePart*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimeApplicationPkcs7Mime
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimeApplicationPkcs7Mime: public MimePart {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a MimeApplicationPkcs7Mime
|
||||||
|
*
|
||||||
|
* @param obj an Object of the right type
|
||||||
|
*/
|
||||||
|
MimeApplicationPkcs7Mime(const Object& obj): MimePart(obj) {
|
||||||
|
if (!is_mime_application_pkcs7_mime())
|
||||||
|
throw std::runtime_error("not a mime-application-pkcs7-mime");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum struct SecureMimeType {
|
||||||
|
CompressedData = GMIME_SECURE_MIME_TYPE_COMPRESSED_DATA,
|
||||||
|
EnvelopedData = GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA,
|
||||||
|
SignedData = GMIME_SECURE_MIME_TYPE_SIGNED_DATA,
|
||||||
|
CertsOnly = GMIME_SECURE_MIME_TYPE_CERTS_ONLY,
|
||||||
|
Unknown = GMIME_SECURE_MIME_TYPE_UNKNOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
SecureMimeType smime_type() const {
|
||||||
|
return static_cast<SecureMimeType>(
|
||||||
|
g_mime_application_pkcs7_mime_get_smime_type(self()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimeApplicationPkcs7Mime* self() const {
|
||||||
|
return reinterpret_cast<GMimeApplicationPkcs7Mime*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimeMultiPart
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimeMultipart: public MimeObject {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a MimeMultipart
|
||||||
|
*
|
||||||
|
* @param obj an Object of the right type
|
||||||
|
*/
|
||||||
|
MimeMultipart(const Object& obj): MimeObject(obj) {
|
||||||
|
if (!is_multipart())
|
||||||
|
throw std::runtime_error("not a mime-multipart");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimeMultipart* self() const {
|
||||||
|
return reinterpret_cast<GMimeMultipart*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimeMultiPartEncrypted
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimeMultipartEncrypted: public MimeMultipart {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a MimeMultipartEncrypted
|
||||||
|
*
|
||||||
|
* @param obj an Object of the right type
|
||||||
|
*/
|
||||||
|
MimeMultipartEncrypted(const Object& obj): MimeMultipart(obj) {
|
||||||
|
if (!is_multipart_encrypted())
|
||||||
|
throw std::runtime_error("not a mime-multipart-encrypted");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimeMultipartEncrypted* self() const {
|
||||||
|
return reinterpret_cast<GMimeMultipartEncrypted*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin wrapper around a GMimeMultiPartSigned
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MimeMultipartSigned: public MimeMultipart {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a MimeMultipartSigned
|
||||||
|
*
|
||||||
|
* @param obj an Object of the right type
|
||||||
|
*/
|
||||||
|
MimeMultipartSigned(const Object& obj): MimeMultipart(obj) {
|
||||||
|
if (!is_multipart_signed())
|
||||||
|
throw std::runtime_error("not a mime-multipart-signed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GMimeMultipartSigned* self() const {
|
||||||
|
return reinterpret_cast<GMimeMultipartSigned*>(object());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Mu
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* MU_MIME_OBJECT_HH__ */
|
||||||
Reference in New Issue
Block a user