mirror of https://github.com/djcb/mu.git
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:
parent
55402622b9
commit
37988b5a26
|
@ -20,6 +20,8 @@ lib_mu_message=static_library(
|
|||
[
|
||||
'mu-message.cc',
|
||||
'mu-message.hh',
|
||||
'mu-message-part.cc',
|
||||
'mu-message-part.hh',
|
||||
'mu-contact.hh',
|
||||
'mu-contact.cc',
|
||||
'mu-document.cc',
|
||||
|
@ -29,7 +31,9 @@ lib_mu_message=static_library(
|
|||
'mu-flags.hh',
|
||||
'mu-flags.cc',
|
||||
'mu-priority.hh',
|
||||
'mu-priority.cc'
|
||||
'mu-priority.cc',
|
||||
'mu-mime-object.cc',
|
||||
'mu-mime-object.hh'
|
||||
],
|
||||
dependencies: [
|
||||
glib_dep,
|
||||
|
@ -39,12 +43,11 @@ lib_mu_message=static_library(
|
|||
lib_mu_utils_dep],
|
||||
install: false)
|
||||
|
||||
# some of the libme headers include xapian
|
||||
xapian_incs = xapian_dep.get_pkgconfig_variable('includedir')
|
||||
lib_mu_message_dep = declare_dependency(
|
||||
link_with: lib_mu_message,
|
||||
dependencies: [ xapian_dep, gmime_dep ],
|
||||
include_directories:
|
||||
include_directories(['.', '..', xapian_incs]))
|
||||
include_directories(['.', '..']))
|
||||
|
||||
#
|
||||
# tests
|
||||
|
@ -77,6 +80,14 @@ test('test-flags',
|
|||
install: false,
|
||||
cpp_args: ['-DBUILD_TESTS'],
|
||||
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',
|
||||
executable('test-priority',
|
||||
'mu-priority.cc',
|
||||
|
|
|
@ -36,7 +36,7 @@ Contact::display_name() const
|
|||
|
||||
Mu::Contacts
|
||||
Mu::make_contacts(InternetAddressList* addr_lst,
|
||||
Field::Id field_id, ::time_t message_date)
|
||||
Field::Id field_id, int64_t message_date)
|
||||
{
|
||||
Contacts contacts;
|
||||
size_t num{};
|
||||
|
@ -70,7 +70,7 @@ Mu::make_contacts(InternetAddressList* addr_lst,
|
|||
Mu::Contacts
|
||||
Mu::make_contacts(const std::string& addrs,
|
||||
Field::Id field_id,
|
||||
::time_t message_date)
|
||||
int64_t message_date)
|
||||
{
|
||||
auto addr_list = internet_address_list_parse(NULL, addrs.c_str());
|
||||
if (!addr_list) {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
#include <utils/mu-option.hh>
|
||||
#include "mu-fields.hh"
|
||||
|
||||
struct _InternetAddressList;
|
||||
|
@ -54,7 +55,7 @@ struct Contact {
|
|||
* @param message_date_ data for the message for this contact
|
||||
*/
|
||||
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)
|
||||
: email{email_}, name{name_}, field_id{field_id_},
|
||||
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
|
||||
* is for use with container classes. This uses the _lowercase_ email
|
||||
* * is for use with container classes. This uses the _lowercase_ email
|
||||
* address.
|
||||
*
|
||||
* @return the hash
|
||||
|
@ -118,14 +119,14 @@ struct Contact {
|
|||
* data members
|
||||
*/
|
||||
|
||||
std::string email; /**< Email address for this contact.Not empty */
|
||||
std::string name; /**< Name for this contact; can be empty. */
|
||||
std::optional<Field::Id> field_id; /**< Field Id of contact or nullopt */
|
||||
::time_t message_date; /**< date of the message from which the
|
||||
* contact originates */
|
||||
bool personal; /**< A personal message? */
|
||||
size_t frequency; /**< Frequency of this contact */
|
||||
int64_t tstamp; /**< Timestamp for this contact */
|
||||
std::string email; /**< Email address for this contact.Not empty */
|
||||
std::string name; /**< Name for this contact; can be empty. */
|
||||
Option<Field::Id> field_id; /**< Field Id of contact or nullopt */
|
||||
int64_t message_date; /**< date of the message from which the
|
||||
* contact originates (or 0) */
|
||||
bool personal; /**< A personal message? */
|
||||
size_t frequency; /**< Frequency of this contact */
|
||||
int64_t tstamp; /**< Timestamp for this contact (internal use) */
|
||||
|
||||
private:
|
||||
void cleanup_name() { // replace control characters by spaces.
|
||||
|
@ -149,7 +150,7 @@ using Contacts = std::vector<Contact>;
|
|||
*/
|
||||
Contacts
|
||||
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
|
||||
|
@ -162,7 +163,7 @@ make_contacts(/*const*/ struct _InternetAddressList* addr_lst,
|
|||
*/
|
||||
Contacts
|
||||
make_contacts(const std::string& addrs,
|
||||
Field::Id field_id, ::time_t message_date);
|
||||
Field::Id field_id, int64_t message_date);
|
||||
} // namespace Mu
|
||||
|
||||
/**
|
||||
|
|
|
@ -69,8 +69,10 @@ Document::add(Field::Id id, const std::string& val)
|
|||
void
|
||||
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())
|
||||
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
|
||||
Document::add(Field::Id id, const Contacts& contacts)
|
||||
{
|
||||
if (contacts.empty())
|
||||
return;
|
||||
|
||||
const auto field{field_from_id(id)};
|
||||
std::vector<std::string> cvec;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "mu-priority.hh"
|
||||
#include "mu-flags.hh"
|
||||
#include "mu-contact.hh"
|
||||
#include <utils/mu-option.hh>
|
||||
|
||||
namespace Mu {
|
||||
|
||||
|
@ -93,7 +94,6 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* updating a document with terms & values
|
||||
*/
|
||||
|
@ -107,7 +107,7 @@ public:
|
|||
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 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 contacts message contacts
|
||||
*/
|
||||
void add(Field::Id id, const Contacts& contacts);
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Get a message-field as a string-value
|
||||
*
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
#include <string_view>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <xapian.h>
|
||||
#include <utils/mu-utils.hh>
|
||||
#include <utils/mu-option.hh>
|
||||
|
||||
namespace Mu {
|
||||
|
||||
|
@ -320,7 +320,7 @@ static constexpr std::array<Field, Field::id_size()>
|
|||
Field::Type::String,
|
||||
"msgid",
|
||||
"Attachment MIME-type",
|
||||
"mime:image/jpeg",
|
||||
"msgid:abc@123",
|
||||
'i',
|
||||
Field::Flag::GMime |
|
||||
Field::Flag::NormalTerm |
|
||||
|
@ -502,11 +502,11 @@ void field_for_each(Func&& func) {
|
|||
* @return a message-field id, or nullopt if not found.
|
||||
*/
|
||||
template <typename Pred>
|
||||
std::optional<Field> field_find_if(Pred&& pred) {
|
||||
Option<Field> field_find_if(Pred&& pred) {
|
||||
for (auto&& field: Fields)
|
||||
if (pred(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.
|
||||
*/
|
||||
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.shortcut == shortcut;
|
||||
});
|
||||
}
|
||||
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)
|
||||
return field_from_shortcut(name[0]);
|
||||
else
|
||||
|
@ -540,10 +540,10 @@ std::optional<Field> field_from_name(const std::string& name) {
|
|||
* @return Field::Id or nullopt
|
||||
*/
|
||||
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_))
|
||||
return std::nullopt;
|
||||
return Nothing;
|
||||
else
|
||||
return field_from_id(static_cast<Field::Id>(id));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/mu-option.hh"
|
||||
#include "mu-contact.hh"
|
||||
#include "mu-priority.hh"
|
||||
#include "mu-flags.hh"
|
||||
#include "mu-fields.hh"
|
||||
#include "mu-document.hh"
|
||||
#include "mu-message-part.hh"
|
||||
|
||||
struct _GMimeMessage;
|
||||
#include "utils/mu-result.hh"
|
||||
|
||||
namespace Mu {
|
||||
|
||||
class Message {
|
||||
public:
|
||||
/**
|
||||
* Move CTOR
|
||||
*
|
||||
* @param some other message
|
||||
*/
|
||||
Message(Message&& msg);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* pass NULL for this parameter, in which case some maildir-specific
|
||||
* 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
|
||||
*
|
||||
* @param doc
|
||||
*/
|
||||
Message(Document& doc): doc_{doc} {}
|
||||
|
||||
/**
|
||||
* Copy CTOR
|
||||
*
|
||||
* @param rhs a Message
|
||||
* @return a message or an error
|
||||
*/
|
||||
Message(const Message& rhs) {
|
||||
*this = rhs;
|
||||
static Result<Message> make_from_document(Document& doc) try {
|
||||
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
|
||||
*/
|
||||
|
||||
Message(Message&& rhs) {
|
||||
*this = std::move(rhs);
|
||||
static Result<Message> make_from_string(const std::string& text) try {
|
||||
return Ok(Message{text});
|
||||
} catch (Error& err) {
|
||||
return Err(err);
|
||||
} catch (...) {
|
||||
return Err(Mu::Error(Error::Code::Message, "failed to create message"));
|
||||
}
|
||||
|
||||
/**
|
||||
* DTOR
|
||||
*
|
||||
*/
|
||||
~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.
|
||||
|
@ -102,7 +105,7 @@ public:
|
|||
*
|
||||
* @return document
|
||||
*/
|
||||
const Document& document() const { return doc_; }
|
||||
const Document& document() const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
|
@ -161,7 +164,7 @@ public:
|
|||
* @return the Message-Id of this message (without the enclosing <>), or
|
||||
* 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
|
||||
|
@ -170,7 +173,7 @@ public:
|
|||
* @return the mailing list id for this message (without the enclosing <>)
|
||||
* 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
|
||||
|
@ -178,14 +181,14 @@ public:
|
|||
* @return message date/time or 0 in case of error or if there
|
||||
* 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
|
||||
*
|
||||
* @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,
|
||||
|
@ -194,14 +197,14 @@ public:
|
|||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
|
@ -212,7 +215,7 @@ public:
|
|||
* @return a vec with the references for this msg.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @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:
|
||||
Document doc_;
|
||||
mutable struct _GMimeMessage *mime_msg_{};
|
||||
Message(const std::string& path, const std::string& mdir);
|
||||
Message(const std::string& str);
|
||||
Message(Document& doc);
|
||||
|
||||
|
||||
std::unique_ptr<Private> priv_;
|
||||
}; // Message
|
||||
} // Mu
|
||||
#endif /* MU_MESSAGE_HH__ */
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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__ */
|
Loading…
Reference in New Issue