lib: remove the old mu-msg* code

Replaced by Mu::Message & friends
This commit is contained in:
Dirk-Jan C. Binnema 2022-04-22 08:00:58 +03:00
parent 85a3ff71ae
commit de07df77d3
13 changed files with 1 additions and 4585 deletions

View File

@ -34,13 +34,7 @@ lib_mu=static_library(
'mu-store.cc',
'mu-tokenizer.cc',
'mu-xapian.cc',
'mu-maildir.cc',
'mu-msg-crypto.cc',
'mu-msg-doc.cc',
'mu-msg-file.cc',
'mu-msg-part.cc',
'mu-msg-sexp.cc',
'mu-msg.cc',
'mu-maildir.cc'
],
dependencies: [
glib_dep,

View File

@ -22,8 +22,6 @@
#include "mu-message.hh"
#include "mu-query-results.hh"
#include "utils/mu-str.h"
#include "mu-msg.hh"
#include "mu-msg-part.hh"
#include "mu-maildir.hh"
#include "utils/mu-utils.hh"

View File

@ -1,343 +0,0 @@
/*
** Copyright (C) 2012-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 "config.h"
#include <string.h>
#include <utils/mu-utils.hh>
#include "gmime/gmime-signature.h"
#include "mu-msg.hh"
#include "mu-msg-priv.hh"
#include "mu-msg-part.hh"
#include <gmime/gmime.h>
#include <gmime/gmime-multipart-signed.h>
using namespace Mu;
static const char*
get_pubkey_algo_name(GMimePubKeyAlgo algo)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (algo) {
case GMIME_PUBKEY_ALGO_DEFAULT: return "default";
case GMIME_PUBKEY_ALGO_RSA: return "RSA";
case GMIME_PUBKEY_ALGO_RSA_E: return "RSA (encryption only)";
case GMIME_PUBKEY_ALGO_RSA_S: return "RSA (signing only)";
case GMIME_PUBKEY_ALGO_ELG_E: return "ElGamal (encryption only)";
case GMIME_PUBKEY_ALGO_DSA: return "DSA";
case GMIME_PUBKEY_ALGO_ELG: return "ElGamal";
default: return "unknown pubkey algorithm";
}
#pragma GCC diagnostic pop
}
static const gchar*
get_digestkey_algo_name(GMimeDigestAlgo algo)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (algo) {
case GMIME_DIGEST_ALGO_DEFAULT: return "default";
case GMIME_DIGEST_ALGO_MD5: return "MD5";
case GMIME_DIGEST_ALGO_SHA1: return "SHA-1";
case GMIME_DIGEST_ALGO_RIPEMD160: return "RIPEMD160";
case GMIME_DIGEST_ALGO_MD2: return "MD2";
case GMIME_DIGEST_ALGO_TIGER192: return "TIGER-192";
case GMIME_DIGEST_ALGO_HAVAL5160: return "HAVAL-5-160";
case GMIME_DIGEST_ALGO_SHA256: return "SHA-256";
case GMIME_DIGEST_ALGO_SHA384: return "SHA-384";
case GMIME_DIGEST_ALGO_SHA512: return "SHA-512";
case GMIME_DIGEST_ALGO_SHA224: return "SHA-224";
case GMIME_DIGEST_ALGO_MD4: return "MD4";
default: return "unknown digest algorithm";
}
#pragma GCC diagnostic pop
}
/* get data from the 'certificate' */
static char*
get_cert_data(GMimeCertificate* cert)
{
const char /**email,*/ *name, *digest_algo, *pubkey_algo, *keyid, *trust;
/* email = g_mime_certificate_get_email (cert); */
name = g_mime_certificate_get_name(cert);
keyid = g_mime_certificate_get_key_id(cert);
digest_algo = get_digestkey_algo_name(g_mime_certificate_get_digest_algo(cert));
pubkey_algo = get_pubkey_algo_name(g_mime_certificate_get_pubkey_algo(cert));
switch (g_mime_certificate_get_trust(cert)) {
case GMIME_TRUST_UNKNOWN: trust = "unknown"; break;
case GMIME_TRUST_UNDEFINED: trust = "undefined"; break;
case GMIME_TRUST_NEVER: trust = "never"; break;
case GMIME_TRUST_MARGINAL: trust = "marginal"; break;
case GMIME_TRUST_FULL: trust = "full"; break;
case GMIME_TRUST_ULTIMATE: trust = "ultimate"; break;
default: g_return_val_if_reached(NULL);
}
return g_strdup_printf("signer:%s, key:%s (%s,%s), trust:%s",
name ? name : "?",
/* email ? email : "?", */
keyid,
pubkey_algo,
digest_algo,
trust);
}
static char*
get_signature_status(GMimeSignatureStatus status)
{
size_t n;
GString* descr;
struct {
GMimeSignatureStatus status;
const char* name;
} status_info[] = {
{GMIME_SIGNATURE_STATUS_VALID, "valid"},
{GMIME_SIGNATURE_STATUS_GREEN, "green"},
{GMIME_SIGNATURE_STATUS_RED, "red"},
{GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key revoked"},
{GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key expired"},
{GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "signature expired"},
{GMIME_SIGNATURE_STATUS_KEY_MISSING, "key missing"},
{GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl missing"},
{GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl too old"},
{GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad policy"},
{GMIME_SIGNATURE_STATUS_SYS_ERROR, "system error"},
{GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu conflict "},
};
descr = g_string_new("");
for (n = 0; n != G_N_ELEMENTS(status_info); ++n) {
if (!(status & status_info[n].status))
continue;
g_string_append_printf(descr,
"%s%s",
descr->len > 0 ? ", " : "",
status_info[n].name);
}
return g_string_free(descr, FALSE);
}
/* get a human-readable report about the signature */
static char*
get_verdict_report(GMimeSignature* msig)
{
gchar * certdata, *report, *status;
GMimeSignatureStatus sigstat;
sigstat = g_mime_signature_get_status(msig);
status = get_signature_status(sigstat);
auto date_str = [](time_t t)->std::string {
if (t == 0 || t == static_cast<time_t>(-1))
return "?";
else
return time_to_string("%x", t);
};
const auto created = date_str(g_mime_signature_get_created(msig));
const auto expires = date_str(g_mime_signature_get_expires(msig));
certdata = get_cert_data(g_mime_signature_get_certificate(msig));
report = g_strdup_printf("%s; created:%s, expires:%s, %s",
status,
created.c_str(),
expires.c_str(),
certdata ? certdata : "?");
g_free(certdata);
g_free(status);
return report;
}
static char*
get_signers(GHashTable* signerhash)
{
GString* gstr;
GHashTableIter iter;
char* name;
if (!signerhash || g_hash_table_size(signerhash) == 0)
return NULL;
gstr = g_string_new(NULL);
g_hash_table_iter_init(&iter, signerhash);
while (g_hash_table_iter_next(&iter, reinterpret_cast<void**>(&name), NULL)) {
if (gstr->len != 0)
g_string_append_c(gstr, ',');
gstr = g_string_append(gstr, name);
}
return g_string_free(gstr, FALSE);
}
static MuMsgPartSigStatusReport*
get_status_report(GMimeSignatureList* sigs)
{
int i;
MuMsgPartSigStatus status;
MuMsgPartSigStatusReport* status_report;
char* report;
GHashTable* signerhash;
status = MU_MSG_PART_SIG_STATUS_GOOD; /* let's start positive! */
signerhash = g_hash_table_new(g_str_hash, g_str_equal);
for (i = 0, report = NULL; i != g_mime_signature_list_length(sigs); ++i) {
GMimeSignature* msig;
GMimeCertificate* cert;
GMimeSignatureStatus sigstat;
gchar* rep;
msig = g_mime_signature_list_get_signature(sigs, i);
sigstat = g_mime_signature_get_status(msig);
/* downgrade our expectations */
if ((sigstat & GMIME_SIGNATURE_STATUS_ERROR_MASK) &&
status != MU_MSG_PART_SIG_STATUS_ERROR)
status = MU_MSG_PART_SIG_STATUS_ERROR;
else if ((sigstat & GMIME_SIGNATURE_STATUS_RED) &&
status == MU_MSG_PART_SIG_STATUS_GOOD)
status = MU_MSG_PART_SIG_STATUS_BAD;
rep = get_verdict_report(msig);
report = g_strdup_printf("%s%s%d: %s",
report ? report : "",
report ? "; " : "",
i + 1,
rep);
g_free(rep);
cert = g_mime_signature_get_certificate(msig);
if (cert && g_mime_certificate_get_name(cert))
g_hash_table_add(signerhash, (gpointer)g_mime_certificate_get_name(cert));
}
status_report = g_slice_new0(MuMsgPartSigStatusReport);
status_report->verdict = status;
status_report->report = report;
status_report->signers = get_signers(signerhash);
g_hash_table_unref(signerhash);
return status_report;
}
void
Mu::mu_msg_part_sig_status_report_destroy(MuMsgPartSigStatusReport* report)
{
if (!report)
return;
g_free((char*)report->report);
g_free((char*)report->signers);
g_slice_free(MuMsgPartSigStatusReport, report);
}
static inline void
tag_with_sig_status(GObject* part, MuMsgPartSigStatusReport* report)
{
g_object_set_data_full(part,
SIG_STATUS_REPORT,
report,
(GDestroyNotify)mu_msg_part_sig_status_report_destroy);
}
void
Mu::mu_msg_crypto_verify_part(GMimeMultipartSigned* sig, MuMsgOptions opts, GError** err)
{
/* the signature status */
MuMsgPartSigStatusReport* report;
GMimeSignatureList* sigs;
g_return_if_fail(GMIME_IS_MULTIPART_SIGNED(sig));
sigs = g_mime_multipart_signed_verify(sig, GMIME_VERIFY_NONE, err);
if (!sigs) {
if (err && !*err)
mu_util_g_set_error(err, MU_ERROR_CRYPTO, "verification failed");
return;
}
report = get_status_report(sigs);
g_clear_object(&sigs);
/* tag this part with the signature status check */
tag_with_sig_status(G_OBJECT(sig), report);
}
static inline void
check_decrypt_result(GMimeMultipartEncrypted* part, GMimeDecryptResult* res, GError** err)
{
GMimeSignatureList* sigs;
MuMsgPartSigStatusReport* report;
if (res) {
/* Check if the decrypted part had any embed signatures */
sigs = res->signatures;
if (sigs) {
report = get_status_report(sigs);
g_mime_signature_list_clear(sigs);
/* tag this part with the signature status check */
tag_with_sig_status(G_OBJECT(part), report);
} else {
if (err && !*err)
mu_util_g_set_error(err, MU_ERROR_CRYPTO, "verification failed");
}
g_object_unref(res);
}
}
GMimeObject* /* this is declared in mu-msg-priv.h */
Mu::mu_msg_crypto_decrypt_part(GMimeMultipartEncrypted* enc,
MuMsgOptions opts,
MuMsgPartPasswordFunc func,
gpointer user_data,
GError** err)
{
GMimeObject* dec;
GMimeDecryptResult* res;
g_return_val_if_fail(GMIME_IS_MULTIPART_ENCRYPTED(enc), NULL);
res = NULL;
dec = g_mime_multipart_encrypted_decrypt(enc, GMIME_DECRYPT_NONE, NULL, &res, err);
check_decrypt_result(enc, res, err);
if (!dec) {
if (err && !*err)
mu_util_g_set_error(err, MU_ERROR_CRYPTO, "decryption failed");
return NULL;
}
return dec;
}

View File

@ -1,119 +0,0 @@
/*
** Copyright (C) 2008-2020 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 of the License, 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 <stdlib.h>
#include <iostream>
#include <string.h>
#include <errno.h>
#include <xapian.h>
#include "message/mu-message.hh"
#include "mu-msg-doc.hh"
#include "utils/mu-util.h"
#include "utils/mu-str.h"
#include "utils/mu-utils.hh"
#include "utils/mu-xapian-utils.hh"
using namespace Mu;
struct Mu::MuMsgDoc {
MuMsgDoc(Xapian::Document* doc) : _doc(doc) {}
~MuMsgDoc() { delete _doc; }
const Xapian::Document doc() const { return *_doc; }
private:
Xapian::Document* _doc;
};
MuMsgDoc*
Mu::mu_msg_doc_new(XapianDocument* doc, GError** err)
{
g_return_val_if_fail(doc, NULL);
MuMsgDoc* mdoc =
xapian_try([&] { return new MuMsgDoc((Xapian::Document*)doc); }, (MuMsgDoc*)nullptr);
if (!mdoc)
mu_util_g_set_error(err, MU_ERROR_INTERNAL, "failed to create message doc");
return mdoc;
}
void
Mu::mu_msg_doc_destroy(MuMsgDoc* self)
{
xapian_try([&] { delete self; });
}
gchar*
Mu::mu_msg_doc_get_str_field(MuMsgDoc* self, Field::Id field_id)
{
g_return_val_if_fail(self, NULL);
// disable this check:
// g_return_val_if_fail (mu_msg_field_is_string(field_id), NULL);
// because it's useful to get numerical field as strings,
// for example when sorting (which is much faster if don't
// have to convert to numbers first, esp. when it's a date
// time_t)
return xapian_try(
[&] {
const auto value_no{field_from_id(field_id).value_no()};
const std::string s(self->doc().get_value(value_no));
return s.empty() ? NULL : g_strdup(s.c_str());
},
(gchar*)nullptr);
}
GSList*
Mu::mu_msg_doc_get_str_list_field(MuMsgDoc* self, Field::Id field_id)
{
g_return_val_if_fail(self, NULL);
g_return_val_if_fail(field_from_id(field_id).type == Field::Type::StringList, NULL);
return xapian_try(
[&] {
/* return a comma-separated string as a GSList */
const auto value_no{field_from_id(field_id).value_no()};
const std::string s(self->doc().get_value(value_no));
return s.empty() ? NULL : mu_str_to_list(s.c_str(), ',', TRUE);
},
(GSList*)nullptr);
}
gint64
Mu::mu_msg_doc_get_num_field(MuMsgDoc* self, Field::Id field_id)
{
g_return_val_if_fail(self, -1);
g_return_val_if_fail(field_from_id(field_id).is_numerical(), -1);
return xapian_try(
[&] {
const auto value_no{field_from_id(field_id).value_no()};
const std::string s(self->doc().get_value(value_no));
if (s.empty())
return (gint64)0;
else if (field_id == Field::Id::Date || field_id == Field::Id::Size)
return static_cast<gint64>(strtol(s.c_str(), NULL, 10));
else {
return static_cast<gint64>(Xapian::sortable_unserialise(s));
}
},
(gint64)-1);
}

View File

@ -1,90 +0,0 @@
/*
** Copyright (C) 2012-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_MSG_DOC_HH__
#define MU_MSG_DOC_HH__
#include <glib.h>
#include <utils/mu-util.h>
#include <message/mu-message.hh>
namespace Mu {
struct MuMsgDoc;
/**
* create a new MuMsgDoc instance
*
* @param doc a Xapian::Document* (you'll need to cast the
* Xapian::Document* to XapianDocument*, because only C (not C++) is
* allowed in this header file. MuMsgDoc takes _ownership_ of this pointer;
* don't touch it afterwards
* @param err receives error info, or NULL
*
* @return a new MuMsgDoc instance (free with mu_msg_doc_destroy), or
* NULL in case of error.
*/
MuMsgDoc* mu_msg_doc_new(XapianDocument* doc, GError** err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/**
* destroy a MuMsgDoc instance -- free all the resources. Note, after
* destroying, any strings returned from mu_msg_doc_get_str_field with
* do_free==FALSE are no longer valid
*
* @param self a MuMsgDoc instance
*/
void mu_msg_doc_destroy(MuMsgDoc* self);
/**
* get a string parameter from the msgdoc
*
* @param self a MuMsgDoc instance
* @param mfid a Field::Id for a string field
*
* @return a string for the given field (see do_free), or NULL in case of error.
* free with g_free
*/
gchar* mu_msg_doc_get_str_field(MuMsgDoc* self, Field::Id mfid) G_GNUC_WARN_UNUSED_RESULT;
/**
* get a string-list parameter from the msgdoc
*
* @param self a MuMsgDoc instance
* @param mfid a Field::Id for a string-list field
*
* @return a list for the given field (see do_free), or NULL in case
* of error. free with mu_str_free_list
*/
GSList* mu_msg_doc_get_str_list_field(MuMsgDoc* self, Field::Id mfid) G_GNUC_WARN_UNUSED_RESULT;
/**
*
* get a numeric parameter from the msgdoc
*
* @param self a MuMsgDoc instance
* @param mfid a Field::Id for a numeric field
*
* @return the numerical value, or -1 in case of error. You'll need to
* cast this value to the actual type (e.g. time_t for Field::Id::Date)
*/
gint64 mu_msg_doc_get_num_field(MuMsgDoc* self, Field::Id mfid);
} // namespace Mu
#endif /*MU_MSG_DOC_HH__*/

View File

@ -1,850 +0,0 @@
/*
** Copyright (C) 2012-2021 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 <array>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <gmime/gmime.h>
#include "mu-maildir.hh"
#include "mu-store.hh"
#include "mu-msg-priv.hh"
#include "utils/mu-util.h"
#include "utils/mu-utils.hh"
#include "utils/mu-str.h"
using namespace Mu;
static gboolean
init_file_metadata(MuMsgFile* self, const char* path, const char* mdir, GError** err);
static gboolean init_mime_msg(MuMsgFile* msg, const char* path, GError** err);
MuMsgFile*
Mu::mu_msg_file_new(const char* filepath, const char* mdir, GError** err)
{
MuMsgFile* self;
g_return_val_if_fail(filepath, NULL);
self = g_new0(MuMsgFile, 1);
if (!init_file_metadata(self, filepath, mdir, err)) {
mu_msg_file_destroy(self);
return NULL;
}
if (!init_mime_msg(self, filepath, err)) {
mu_msg_file_destroy(self);
return NULL;
}
return self;
}
void
Mu::mu_msg_file_destroy(MuMsgFile* self)
{
if (!self)
return;
g_clear_object(&self->_mime_msg);
g_free(self->_path);
g_free(self->_maildir);
g_free(self->_sha1);
g_free(self);
}
static gboolean
init_file_metadata(MuMsgFile* self, const char* path, const gchar* mdir, GError** err)
{
struct stat statbuf;
if (access(path, R_OK) != 0) {
mu_util_g_set_error(err,
MU_ERROR_FILE,
"cannot read file %s: %s",
path,
g_strerror(errno));
return FALSE;
}
if (stat(path, &statbuf) < 0) {
mu_util_g_set_error(err,
MU_ERROR_FILE,
"cannot stat %s: %s",
path,
g_strerror(errno));
return FALSE;
}
if (!S_ISREG(statbuf.st_mode)) {
mu_util_g_set_error(err, MU_ERROR_FILE, "not a regular file: %s", path);
return FALSE;
}
self->_timestamp = statbuf.st_mtime;
self->_size = (size_t)statbuf.st_size;
self->_path = g_canonicalize_filename(path, NULL);
self->_maildir = g_strdup(mdir ? mdir : "");
return TRUE;
}
static char*
calculate_sha1(FILE* file)
{
std::array<uint8_t, 4096> buf{};
char* sha1{};
GChecksum* checksum{g_checksum_new(G_CHECKSUM_SHA256)};
while (true) {
const auto n = ::fread(buf.data(), 1, buf.size(), file);
if (n == 0)
break;
g_checksum_update(checksum, buf.data(), n);
}
if (::ferror(file))
g_warning("error reading file");
else
sha1 = g_strdup(g_checksum_get_string(checksum));
g_checksum_free(checksum);
return sha1;
}
static GMimeStream*
get_mime_stream(MuMsgFile* self, const char* path, GError** err)
{
FILE* file;
GMimeStream* stream;
file = fopen(path, "r");
if (!file) {
g_set_error(err,
MU_ERROR_DOMAIN,
MU_ERROR_FILE,
"cannot open %s: %s",
path,
g_strerror(errno));
return NULL;
}
stream = g_mime_stream_file_new(file);
if (!stream) {
g_set_error(err,
MU_ERROR_DOMAIN,
MU_ERROR_GMIME,
"cannot create mime stream for %s",
path);
fclose(file);
return NULL;
}
self->_sha1 = calculate_sha1(file);
if (!self->_sha1) {
::fclose(file);
g_set_error(err,
MU_ERROR_DOMAIN,
MU_ERROR_FILE,
"failed to get sha-1 for %s",
path);
return NULL;
}
return stream;
}
static gboolean
init_mime_msg(MuMsgFile* self, const char* path, GError** err)
{
GMimeStream* stream;
GMimeParser* parser;
stream = get_mime_stream(self, path, err);
if (!stream)
return FALSE;
parser = g_mime_parser_new_with_stream(stream);
g_object_unref(stream);
if (!parser) {
g_set_error(err,
MU_ERROR_DOMAIN,
MU_ERROR_GMIME,
"cannot create mime parser for %s",
path);
return FALSE;
}
self->_mime_msg = g_mime_parser_construct_message(parser, NULL);
g_object_unref(parser);
if (!self->_mime_msg) {
g_set_error(err,
MU_ERROR_DOMAIN,
MU_ERROR_GMIME,
"message seems invalid, ignoring (%s)",
path);
return FALSE;
}
return TRUE;
}
static char*
get_recipient(MuMsgFile* self, GMimeAddressType atype)
{
char* recip;
InternetAddressList* recips;
recips = g_mime_message_get_addresses(self->_mime_msg, atype);
/* FALSE --> don't encode */
recip = (char*)internet_address_list_to_string(recips, NULL, FALSE);
if (recip && !g_utf8_validate(recip, -1, NULL)) {
g_debug("invalid recipient in %s\n", self->_path);
mu_str_asciify_in_place(recip); /* ugly... */
}
if (mu_str_is_empty(recip)) {
g_free(recip);
return NULL;
}
if (recip)
mu_str_remove_ctrl_in_place(recip);
return recip;
}
/*
* let's try to guess the mailing list from some other
* headers in the mail
*/
static gchar*
get_fake_mailing_list_maybe(MuMsgFile* self)
{
const char* hdr;
hdr = g_mime_object_get_header(GMIME_OBJECT(self->_mime_msg), "X-Feed2Imap-Version");
if (!hdr)
return NULL;
/* looks like a feed2imap header; guess the source-blog
* from the msgid */
{
const char *msgid, *e;
msgid = g_mime_message_get_message_id(self->_mime_msg);
if (msgid && (e = strchr(msgid, '-')))
return g_strndup(msgid, e - msgid);
}
return NULL;
}
static gchar*
get_mailing_list(MuMsgFile* self)
{
char * dechdr, *res;
const char *hdr, *b, *e;
hdr = g_mime_object_get_header(GMIME_OBJECT(self->_mime_msg), "List-Id");
if (mu_str_is_empty(hdr))
return get_fake_mailing_list_maybe(self);
dechdr = g_mime_utils_header_decode_phrase(NULL, hdr);
if (!dechdr)
return NULL;
e = NULL;
b = strchr(dechdr, '<');
if (b)
e = strchr(b, '>');
if (b && e)
res = g_strndup(b + 1, e - b - 1);
else
res = g_strdup(dechdr);
g_free(dechdr);
return res;
}
static gboolean
looks_like_attachment(GMimeObject* part)
{
GMimeContentDisposition* disp;
GMimeContentType* ctype;
const char* dispstr;
guint u;
const struct {
const char* type;
const char* sub_type;
} att_types[] = {{"image", "*"},
{"audio", "*"},
{"application", "*"},
{"application", "x-patch"}};
disp = g_mime_object_get_content_disposition(part);
if (!GMIME_IS_CONTENT_DISPOSITION(disp))
return FALSE;
dispstr = g_mime_content_disposition_get_disposition(disp);
if (g_ascii_strcasecmp(dispstr, "attachment") == 0)
return TRUE;
/* we also consider patches, images, audio, and non-pgp-signature
* application attachments to be attachments... */
ctype = g_mime_object_get_content_type(part);
if (g_mime_content_type_is_type(ctype, "*", "pgp-signature"))
return FALSE; /* don't consider as a signature */
if (g_mime_content_type_is_type(ctype, "text", "*")) {
if (g_mime_content_type_is_type(ctype, "*", "plain") ||
g_mime_content_type_is_type(ctype, "*", "html"))
return FALSE;
else
return TRUE;
}
for (u = 0; u != G_N_ELEMENTS(att_types); ++u)
if (g_mime_content_type_is_type(ctype, att_types[u].type, att_types[u].sub_type))
return TRUE;
return FALSE;
}
static void
msg_cflags_cb(GMimeObject* parent, GMimeObject* part, Flags* flags)
{
if (GMIME_IS_MULTIPART_SIGNED(part))
*flags |= Flags::Signed;
/* FIXME: An encrypted part might be signed at the same time.
* In that case the signed flag is lost. */
if (GMIME_IS_MULTIPART_ENCRYPTED(part))
*flags |= Flags::Encrypted;
/* smime */
if (GMIME_IS_APPLICATION_PKCS7_MIME(part)) {
GMimeApplicationPkcs7Mime *pkcs7;
pkcs7 = GMIME_APPLICATION_PKCS7_MIME(part);
if (pkcs7) {
switch(pkcs7->smime_type) {
case GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA:
*flags |= Flags::Encrypted;
break;
case GMIME_SECURE_MIME_TYPE_SIGNED_DATA:
*flags |= Flags::Signed;
break;
default:
break;
}
}
}
if (any_of(*flags & Flags::HasAttachment))
return;
if (!GMIME_IS_PART(part))
return;
if (looks_like_attachment(part))
*flags |= Flags::HasAttachment;
}
static Flags
get_content_flags(MuMsgFile* self)
{
Flags flags{Flags::None};
if (GMIME_IS_MESSAGE(self->_mime_msg)) {
/* toplevel */
msg_cflags_cb(NULL, GMIME_OBJECT(self->_mime_msg), &flags);
/* parts */
mu_mime_message_foreach(self->_mime_msg,
FALSE, /* never decrypt for this */
(GMimeObjectForeachFunc)msg_cflags_cb,
&flags);
}
char *ml{get_mailing_list(self)};
if (ml) {
flags |= Flags::MailingList;
g_free(ml);
}
return flags;
}
static Flags
get_flags(MuMsgFile* self)
{
g_return_val_if_fail(self, Flags::None);
auto flags{mu_maildir_flags_from_path(self->_path)
.value_or(Flags::None)};
flags |= get_content_flags(self);
/* pseudo-flag --> unread means either NEW or NOT SEEN, just
* for searching convenience */
if (any_of(flags & Flags::New) ||
none_of(flags & Flags::Seen))
flags |= Flags::Unread;
return flags;
}
static size_t
get_size(MuMsgFile* self)
{
g_return_val_if_fail(self, 0);
return self->_size;
}
static Priority
parse_prio_str(const char* priostr)
{
int i;
struct {
const char* _str;
Priority _prio;
} str_prio[] = {{"high", Priority::High},
{"1", Priority::High},
{"2", Priority::High},
{"normal", Priority::Normal},
{"3", Priority::Normal},
{"low", Priority::Low},
{"list", Priority::Low},
{"bulk", Priority::Low},
{"4", Priority::Low},
{"5", Priority::Low}};
for (i = 0; i != G_N_ELEMENTS(str_prio); ++i)
if (g_ascii_strcasecmp(priostr, str_prio[i]._str) == 0)
return str_prio[i]._prio;
/* e.g., last-fm uses 'fm-user'... as precedence */
return Priority::Normal;
}
static Priority
get_prio(MuMsgFile* self)
{
GMimeObject* obj;
const char* priostr;
g_return_val_if_fail(self, Priority::Normal);
obj = GMIME_OBJECT(self->_mime_msg);
priostr = g_mime_object_get_header(obj, "Precedence");
if (!priostr)
priostr = g_mime_object_get_header(obj, "X-Priority");
if (!priostr)
priostr = g_mime_object_get_header(obj, "Importance");
if (!priostr)
return Priority::Normal;
else
return parse_prio_str(priostr);
}
/* NOTE: buffer will be *freed* or returned unchanged */
static char*
convert_to_utf8(GMimePart* part, char* buffer)
{
GMimeContentType* ctype;
const char* charset;
ctype = g_mime_object_get_content_type(GMIME_OBJECT(part));
g_return_val_if_fail(GMIME_IS_CONTENT_TYPE(ctype), NULL);
/* of course, the charset specified may be incorrect... */
charset = g_mime_content_type_get_parameter(ctype, "charset");
if (charset) {
char* utf8;
if ((utf8 = mu_str_convert_to_utf8(buffer, g_mime_charset_iconv_name(charset)))) {
g_free(buffer);
buffer = utf8;
}
} else if (!g_utf8_validate(buffer, -1, NULL)) {
/* if it's already utf8, nothing to do otherwise: no
charset at all, or conversion failed; ugly * hack:
replace all non-ascii chars with '.' */
mu_str_asciify_in_place(buffer);
}
return buffer;
}
static gchar*
stream_to_string(GMimeStream* stream, size_t buflen)
{
char* buffer;
ssize_t bytes;
buffer = g_new(char, buflen + 1);
g_mime_stream_reset(stream);
/* we read everything in one go */
bytes = g_mime_stream_read(stream, buffer, buflen);
if (bytes < 0) {
g_warning("%s: failed to read from stream", __func__);
g_free(buffer);
return NULL;
}
buffer[bytes] = '\0';
return buffer;
}
gchar*
Mu::mu_msg_mime_part_to_string(GMimePart* part, gboolean* err)
{
GMimeDataWrapper* wrapper;
GMimeStream* stream;
ssize_t buflen;
char* buffer;
buffer = NULL;
stream = NULL;
g_return_val_if_fail(err, NULL);
*err = TRUE; /* guilty until proven innocent */
g_return_val_if_fail(GMIME_IS_PART(part), NULL);
wrapper = g_mime_part_get_content(part);
if (!wrapper) {
/* this happens with invalid mails */
g_debug("failed to create data wrapper");
goto cleanup;
}
stream = g_mime_stream_mem_new();
if (!stream) {
g_warning("failed to create mem stream");
goto cleanup;
}
buflen = g_mime_data_wrapper_write_to_stream(wrapper, stream);
if (buflen <= 0) { /* empty buffer, not an error */
*err = FALSE;
goto cleanup;
}
buffer = stream_to_string(stream, (size_t)buflen);
/* convert_to_utf8 will free the old 'buffer' if needed */
buffer = convert_to_utf8(part, buffer);
*err = FALSE;
cleanup:
if (G_IS_OBJECT(stream))
g_object_unref(stream);
return buffer;
}
static gboolean
contains(GSList* lst, const char* str)
{
for (; lst; lst = g_slist_next(lst))
if (g_strcmp0((char*)lst->data, str) == 0)
return TRUE;
return FALSE;
}
/*
* NOTE: this will get the list of references with the oldest parent
* at the beginning */
static GSList*
get_references(MuMsgFile* self)
{
GSList* msgids;
unsigned u;
const char* headers[] = {"References", "In-reply-to", NULL};
for (msgids = NULL, u = 0; headers[u]; ++u) {
char* str;
GMimeReferences* mime_refs;
int i, refs_len;
str = mu_msg_file_get_header(self, headers[u]);
if (!str)
continue;
mime_refs = g_mime_references_parse(NULL, str);
g_free(str);
refs_len = g_mime_references_length(mime_refs);
for (i = 0; i < refs_len; ++i) {
const char* msgid;
msgid = g_mime_references_get_message_id(mime_refs, i);
/* don't include duplicates */
if (msgid && !contains(msgids, msgid))
/* explicitly ensure it's utf8-safe,
* as GMime does not ensure that */
msgids = g_slist_prepend(msgids, g_strdup((msgid)));
}
g_mime_references_free(mime_refs);
}
/* reverse, because we used g_slist_prepend for performance
* reasons */
return g_slist_reverse(msgids);
}
/* see: http://does-not-exist.org/mail-archives/mutt-dev/msg08249.html */
static GSList*
get_tags(MuMsgFile* self)
{
GSList* lst;
unsigned u;
struct {
const char* header;
char sepa;
} tagfields[] = {{"X-Label", ' '}, {"X-Keywords", ','}, {"Keywords", ' '}};
for (lst = NULL, u = 0; u != G_N_ELEMENTS(tagfields); ++u) {
gchar* hdr;
hdr = mu_msg_file_get_header(self, tagfields[u].header);
if (hdr) {
GSList* hlst;
hlst = mu_str_to_list(hdr, tagfields[u].sepa, TRUE);
if (lst)
(g_slist_last(lst))->next = hlst;
else
lst = hlst;
g_free(hdr);
}
}
return lst;
}
static char*
cleanup_maybe(const char* str, gboolean* do_free)
{
char* s;
if (!str)
return NULL;
if (!g_utf8_validate(str, -1, NULL)) {
if (*do_free)
s = mu_str_asciify_in_place((char*)str);
else {
*do_free = TRUE;
s = mu_str_asciify_in_place(g_strdup(str));
}
} else
s = (char*)str;
mu_str_remove_ctrl_in_place(s);
return s;
}
G_GNUC_CONST static GMimeAddressType
address_type(Field::Id field_id)
{
switch (field_id) {
case Field::Id::Bcc: return GMIME_ADDRESS_TYPE_BCC;
case Field::Id::Cc: return GMIME_ADDRESS_TYPE_CC;
case Field::Id::To: return GMIME_ADDRESS_TYPE_TO;
case Field::Id::From: return GMIME_ADDRESS_TYPE_FROM;
default: g_return_val_if_reached((GMimeAddressType)-1);
}
}
static gchar*
get_msgid(MuMsgFile* self, gboolean* do_free)
{
const char* msgid{g_mime_message_get_message_id(self->_mime_msg)};
if (msgid && strlen(msgid) < Store::MaxTermLength) {
*do_free = FALSE;
return (char*)msgid;
}
// if there's no valid message-id, synthesize one;
// based on the contents so it stays valid if moved around.
*do_free = TRUE;
return g_strdup_printf("%s@mu", self->_sha1);
}
char*
Mu::mu_msg_file_get_str_field(MuMsgFile* self, Field::Id field_id, gboolean* do_free)
{
g_return_val_if_fail(self, NULL);
g_return_val_if_fail(field_from_id(field_id).is_string(), NULL);
*do_free = FALSE; /* default */
switch (field_id) {
case Field::Id::Bcc:
case Field::Id::Cc:
case Field::Id::From:
case Field::Id::To: *do_free = TRUE; return get_recipient(self, address_type(field_id));
case Field::Id::Path: return self->_path;
case Field::Id::MailingList: *do_free = TRUE; return (char*)get_mailing_list(self);
case Field::Id::Subject:
return (char*)cleanup_maybe(g_mime_message_get_subject(self->_mime_msg), do_free);
case Field::Id::MessageId: return get_msgid(self, do_free);
case Field::Id::Maildir: return self->_maildir;
case Field::Id::BodyText: /* use mu_msg_get_body_text */
case Field::Id::BodyHtml: /* use mu_msg_get_body_html */
case Field::Id::EmbeddedText:
g_warning("%*s is not retrievable through: %s",
STR_V(field_from_id(field_id).name), __func__);
return NULL;
default: g_return_val_if_reached(NULL);
}
}
GSList*
Mu::mu_msg_file_get_str_list_field(MuMsgFile* self, Field::Id field_id)
{
g_return_val_if_fail(self, NULL);
g_return_val_if_fail(field_from_id(field_id).is_string_list(), NULL);
switch (field_id) {
case Field::Id::References: return get_references(self);
case Field::Id::Tags: return get_tags(self);
default: g_return_val_if_reached(NULL);
}
}
gint64
Mu::mu_msg_file_get_num_field(MuMsgFile* self, const Field::Id field_id)
{
g_return_val_if_fail(self, -1);
g_return_val_if_fail(field_from_id(field_id).is_numerical(), -1);
switch (field_id) {
case Field::Id::Date: {
GDateTime* dt;
dt = g_mime_message_get_date(self->_mime_msg);
return dt ? g_date_time_to_unix(dt) : 0;
}
case Field::Id::Flags: return (gint64)get_flags(self);
case Field::Id::Priority: return (gint64)get_prio(self);
case Field::Id::Size: return (gint64)get_size(self);
default: g_return_val_if_reached(-1);
}
}
char*
Mu::mu_msg_file_get_header(MuMsgFile* self, const char* header)
{
const gchar* hdr;
g_return_val_if_fail(self, NULL);
g_return_val_if_fail(header, NULL);
/* sadly, g_mime_object_get_header may return non-ascii;
* so, we need to ensure that
*/
hdr = g_mime_object_get_header(GMIME_OBJECT(self->_mime_msg), header);
return hdr ? mu_str_utf8ify(hdr) : NULL;
}
struct _ForeachData {
GMimeObjectForeachFunc user_func;
gpointer user_data;
gboolean decrypt;
};
typedef struct _ForeachData ForeachData;
static void
foreach_cb(GMimeObject* parent, GMimeObject* part, ForeachData* fdata)
{
/* invoke the callback function */
fdata->user_func(parent, part, fdata->user_data);
/* maybe iterate over decrypted parts */
if (fdata->decrypt && GMIME_IS_MULTIPART_ENCRYPTED(part)) {
GMimeObject* dec;
dec = mu_msg_crypto_decrypt_part(GMIME_MULTIPART_ENCRYPTED(part),
MU_MSG_OPTION_NONE,
NULL,
NULL,
NULL);
if (!dec)
return;
if (GMIME_IS_MULTIPART(dec))
g_mime_multipart_foreach((GMIME_MULTIPART(dec)),
(GMimeObjectForeachFunc)foreach_cb,
fdata);
else
foreach_cb(parent, dec, fdata);
g_object_unref(dec);
}
}
void
Mu::mu_mime_message_foreach(GMimeMessage* msg,
gboolean decrypt,
GMimeObjectForeachFunc func,
gpointer user_data)
{
ForeachData fdata;
g_return_if_fail(GMIME_IS_MESSAGE(msg));
g_return_if_fail(func);
fdata.user_func = func;
fdata.user_data = user_data;
fdata.decrypt = decrypt;
g_mime_message_foreach(msg, (GMimeObjectForeachFunc)foreach_cb, &fdata);
}

View File

@ -1,100 +0,0 @@
/*
** Copyright (C) 2012-2020 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_MSG_FILE_HH__
#define MU_MSG_FILE_HH__
#include "message/mu-message.hh"
namespace Mu {
struct MuMsgFile;
/**
* create a new message from a file
*
* @param path full path to the message
* @param mdir
* @param err error to receive (when function returns NULL), or NULL
*
* @return a new MuMsg, or NULL in case of error
*/
MuMsgFile* mu_msg_file_new(const char* path,
const char* mdir,
GError** err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/**
* destroy a MuMsgFile object
*
* @param self object to destroy, or NULL
*/
void mu_msg_file_destroy(MuMsgFile* self);
/**
* get a specific header
*
* @param self a MuMsgFile instance
* @param header a header (e.g. 'X-Mailer' or 'List-Id')
*
* @return the value of the header or NULL if not found; free with g_free
*/
char* mu_msg_file_get_header(MuMsgFile* self, const char* header);
/**
* get a string value for this message
*
* @param self a valid MuMsgFile
* @param msfid the message field id to get (must be of type string)
* @param do_free receives TRUE or FALSE, conveying if this string
* should be owned & freed (TRUE) or not by caller. In case 'FALSE',
* this function should be treated as if it were returning a const
* char*, and note that in that case the string is only valid as long
* as the MuMsgFile is alive, ie. before mu_msg_file_destroy
*
* @return a string, or NULL
*/
char* mu_msg_file_get_str_field(MuMsgFile* self,
Field::Id msfid,
gboolean* do_free) G_GNUC_WARN_UNUSED_RESULT;
/**
* get a string-list value for this message
*
* @param self a valid MuMsgFile
* @param msfid the message field id to get (must be of type string-list)
*
* @return a GSList*, or NULL; free with mu_str_free_list
*/
GSList* mu_msg_file_get_str_list_field(MuMsgFile* self,
Field::Id msfid) G_GNUC_WARN_UNUSED_RESULT;
/**
* get a numeric value for this message -- the return value should be
* cast into the actual type, e.g., time_t, MuMsgPrio etc.
*
* @param self a valid MuMsgFile
* @param msfid the message field id to get (must be string-based one)
*
* @return the numeric value, or -1 in case of error
*/
gint64 mu_msg_file_get_num_field(MuMsgFile* self, Field::Id mfid);
} // namespace Mu
#endif /*MU_MSG_FILE_HH__*/

File diff suppressed because it is too large Load Diff

View File

@ -1,248 +0,0 @@
/*
** Copyright (C) 2008-2020 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_MSG_PART_HH__
#define MU_MSG_PART_HH__
#include "utils/mu-utils.hh"
#include <glib.h>
#include <unistd.h> /* for ssize_t */
namespace Mu {
#define SIG_STATUS_REPORT "sig-status-report"
enum MuMsgPartType {
MU_MSG_PART_TYPE_NONE = 0,
/* MIME part without children */
MU_MSG_PART_TYPE_LEAF = 1 << 1,
/* an RFC822 message part? */
MU_MSG_PART_TYPE_MESSAGE = 1 << 2,
/* disposition inline? */
MU_MSG_PART_TYPE_INLINE = 1 << 3,
/* disposition attachment? */
MU_MSG_PART_TYPE_ATTACHMENT = 1 << 4,
/* a signed part? */
MU_MSG_PART_TYPE_SIGNED = 1 << 5,
/* an encrypted part? */
MU_MSG_PART_TYPE_ENCRYPTED = 1 << 6,
/* a decrypted part? */
MU_MSG_PART_TYPE_DECRYPTED = 1 << 7,
/* a text/plain part? */
MU_MSG_PART_TYPE_TEXT_PLAIN = 1 << 8,
/* a text/html part? */
MU_MSG_PART_TYPE_TEXT_HTML = 1 << 9
};
MU_ENABLE_BITOPS(MuMsgPartType);
/* the signature status */
enum _MuMsgPartSigStatus {
MU_MSG_PART_SIG_STATUS_UNSIGNED = 0,
MU_MSG_PART_SIG_STATUS_GOOD,
MU_MSG_PART_SIG_STATUS_BAD,
MU_MSG_PART_SIG_STATUS_ERROR,
MU_MSG_PART_SIG_STATUS_FAIL
};
typedef enum _MuMsgPartSigStatus MuMsgPartSigStatus;
typedef struct {
MuMsgPartSigStatus verdict;
const char* report;
const char* signers;
} MuMsgPartSigStatusReport;
/**
* destroy a MuMsgPartSignatureStatusReport object
*
* @param report a MuMsgPartSignatureStatusReport object
*/
void mu_msg_part_sig_status_report_destroy(MuMsgPartSigStatusReport* report);
struct _MuMsgPart {
/* index of this message part */
unsigned index;
/* cid */
/* const char *content_id; */
/* content-type: type/subtype, ie. text/plain */
const char* type;
const char* subtype;
/* size of the part; or < 0 if unknown */
ssize_t size;
gpointer data; /* opaque data */
MuMsgPartType part_type;
MuMsgPartSigStatusReport* sig_status_report;
};
typedef struct _MuMsgPart MuMsgPart;
/**
* get some appropriate file name for the mime-part
*
* @param mpart a MuMsgPart
* @param construct_if_needed if there is no
* real filename, construct one.
*
* @return the file name (free with g_free)
*/
char* mu_msg_part_get_filename(MuMsgPart* mpart,
gboolean construct_if_needed) G_GNUC_WARN_UNUSED_RESULT;
/**
* get appropriate content id for the mime-part
*
* @param mpart a MuMsgPart
*
* @return const content id
*/
const gchar* mu_msg_part_get_content_id(MuMsgPart* mpart) G_GNUC_WARN_UNUSED_RESULT;
/**
* get the text in the MuMsgPart (ie. in its GMimePart)
*
* @param msg a MuMsg
* @param part a MuMsgPart
* @param opts MuMsgOptions
*
* @return utf8 string for this MIME part, to be freed by caller
*/
char*
mu_msg_part_get_text(MuMsg* msg, MuMsgPart* part, MuMsgOptions opts) G_GNUC_WARN_UNUSED_RESULT;
/**
* does this msg part look like an attachment?
*
* @param part a message part
*
* @return TRUE if it looks like an attachment, FALSE otherwise
*/
gboolean mu_msg_part_maybe_attachment(MuMsgPart* part);
/**
* save a specific attachment to some targetdir
*
* @param msg a valid MuMsg instance
* @param opts mu-message options (OVERWRITE/USE_EXISTING)
* @gchar filepath the filepath to save
* @param partidx index of the attachment you want to save
* @param err receives error information (when function returns NULL)
*
* @return full path to the message part saved or NULL in case or
* error; free with g_free
*/
gboolean
mu_msg_part_save(MuMsg* msg, MuMsgOptions opts, const char* filepath, guint partidx, GError** err);
/**
* save a message part to a temporary file and return the full path to
* this file
*
* @param msg a MuMsg message
* @param opts mu-message options (OVERWRITE/USE_EXISTING)
* @param partidx index of the part to save
* @param err receives error information if any
*
* @return the full path to the temp file, or NULL in case of error
*/
gchar* mu_msg_part_save_temp(MuMsg* msg, MuMsgOptions opts, guint partidx, GError** err)
G_GNUC_WARN_UNUSED_RESULT;
/**
* get a filename for the saving the message part; try the filename
* specified for the message part if any, otherwise determine a unique
* name based on the partidx and the message path
*
* @param msg a msg
* @param opts mu-message options
* @param targetdir where to store the part
* @param partidx the part for which to determine a filename
* @param err receives error information (when function returns NULL)
*
* @return a filepath (g_free when done with it) or NULL in case of error
*/
gchar* mu_msg_part_get_path(MuMsg* msg,
MuMsgOptions opts,
const char* targetdir,
guint partidx,
GError** err) G_GNUC_WARN_UNUSED_RESULT;
/**
* get a full path name for a file for saving the message part INDEX;
* this path is unique (1:1) for this particular message and part for
* this user. Thus, it can be used as a cache.
*
* Will create the directory if needed.
*
* @param msg a msg
* @param opts mu-message options
* @param partidx the part for which to determine a filename
* @param err receives error information (when function returns NULL)
*
* @return a filepath (g_free when done with it) or NULL in case of error
*/
gchar* mu_msg_part_get_cache_path(MuMsg* msg, MuMsgOptions opts, guint partidx, GError** err)
G_GNUC_WARN_UNUSED_RESULT;
/**
* get the part index for the message part with a certain content-id
*
* @param msg a message
* @param content_id a content-id to search
*
* @return the part index number of the found part, or -1 if it was not found
*/
int mu_msg_find_index_for_cid(MuMsg* msg, MuMsgOptions opts, const char* content_id);
/**
* retrieve a list of indices for mime-parts with filenames matching a regex
*
* @param msg a message
* @param opts
* @param a regular expression to match the filename with
*
* @return a list with indices for the files matching the pattern; the
* indices are the GPOINTER_TO_UINT(lst->data) of the list. They must
* be freed with g_slist_free
*/
GSList* mu_msg_find_files(MuMsg* msg, MuMsgOptions opts, const GRegex* pattern);
typedef void (*MuMsgPartForeachFunc)(MuMsg* msg, MuMsgPart*, gpointer);
/**
* call a function for each of the mime part in a message
*
* @param msg a valid MuMsg* instance
* @param func a callback function to call for each contact; when
* the callback does not return TRUE, it won't be called again
* @param user_data a user-provide pointer that will be passed to the callback
* @param options, bit-wise OR'ed
*
* @return FALSE in case of error, TRUE otherwise
*/
gboolean
mu_msg_part_foreach(MuMsg* msg, MuMsgOptions opts, MuMsgPartForeachFunc func, gpointer user_data);
} // namespace Mu
#endif /*MU_MSG_PART_HH__*/

View File

@ -1,132 +0,0 @@
/*
** Copyright (C) 2008-2020 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 of the License, 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_MSG_PRIV_HH__
#define MU_MSG_PRIV_HH__
#include <gmime/gmime.h>
#include <stdlib.h>
#include <mu-msg.hh>
#include <mu-msg-file.hh>
#include <mu-msg-doc.hh>
#include "mu-msg-part.hh"
namespace Mu {
struct MuMsgFile {
GMimeMessage* _mime_msg;
time_t _timestamp;
size_t _size;
char* _path;
char* _maildir;
char* _sha1;
};
/* we put the the MuMsg definition in this separate -priv file, so we
* can split the mu_msg implementations over separate files */
struct MuMsg {
guint _refcount;
/* our two backend */
MuMsgFile* _file; /* based on GMime, ie. a file on disc */
MuMsgDoc* _doc; /* based on Xapian::Document */
/* lists where we push allocated strings / GSLists of string
* so we can free them when the struct gets destroyed (and we
* can return them as 'const to callers)
*/
GSList* _free_later_str;
GSList* _free_later_lst;
};
/**
* convert a GMimePart to a string
*
* @param part a GMimePart
* @param err will receive TRUE if there was an error, FALSE
* otherwise. Must NOT be NULL.
*
* @return utf8 string for this MIME part, to be freed by caller
*/
gchar* mu_msg_mime_part_to_string(GMimePart* part,
gboolean* err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/**
* Like g_mime_message_foreach, but will recurse into encrypted parts
* if @param decrypt is TRUE and mu was built with crypto support
*
* @param msg a GMimeMessage
* @param decrypt whether to try to automatically decrypt
* @param func user callback function for each part
* @param user_data user point passed to callback function
* @param err receives error information
*
*/
void mu_mime_message_foreach(GMimeMessage* msg,
gboolean decrypt,
GMimeObjectForeachFunc func,
gpointer user_data);
/**
* callback function to retrieve a password from the user
*
* @param user_id the user name / id to get the password for
* @param prompt_ctx a string containing some helpful context for the prompt
* @param reprompt whether this is a reprompt after an earlier, incorrect password
* @param user_data the user_data pointer passed to mu_msg_part_decrypt_foreach
*
* @return a newly allocated (g_free'able) string
*/
typedef char* (*MuMsgPartPasswordFunc)(const char* user_id,
const char* prompt_ctx,
gboolean reprompt,
gpointer user_data);
/**
* verify the signature of a signed message part
*
* @param sig a signed message part
* @param opts message options
* @param err receive error information
*
* @return a status report object, free with mu_msg_part_sig_status_report_destroy
*/
void mu_msg_crypto_verify_part(GMimeMultipartSigned* sig, MuMsgOptions opts, GError** err);
/**
* decrypt the given encrypted mime multipart
*
* @param enc encrypted part
* @param opts options
* @param password_func callback function to retrieve as password (or NULL)
* @param user_data pointer passed to the password func
* @param err receives error data
*
* @return the decrypted part, or NULL in case of error
*/
GMimeObject* mu_msg_crypto_decrypt_part(GMimeMultipartEncrypted* enc,
MuMsgOptions opts,
MuMsgPartPasswordFunc func,
gpointer user_data,
GError** err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
} // namespace Mu
#endif /*MU_MSG_PRIV_HH__*/

View File

@ -1,390 +0,0 @@
/*
** Copyright (C) 2011-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 <string.h>
#include <ctype.h>
#include "message/mu-message.hh"
#include "mu-query-results.hh"
#include "utils/mu-str.h"
#include "mu-msg.hh"
#include "mu-msg-part.hh"
#include "mu-maildir.hh"
using namespace Mu;
static void
add_prop_nonempty(Sexp::List& list, const char* elm, const GSList* str_lst)
{
Sexp::List elms;
while (str_lst) {
elms.add(Sexp::make_string((const char*)str_lst->data));
str_lst = g_slist_next(str_lst);
}
if (!elms.empty())
list.add_prop(elm, Sexp::make_list(std::move(elms)));
}
static void
add_prop_nonempty(Sexp::List& list, const char* name, const char* str)
{
if (str && str[0])
list.add_prop(name, Sexp::make_string(str));
}
static Mu::Sexp
make_contact_sexp(const Contact& contact)
{
return Sexp::make_list(
/* name */
Sexp::make_string(contact.name, true/*?nil*/),
/* dot */
Sexp::make_symbol("."),
/* email */
Sexp::make_string(contact.email));
}
static void
add_list_post(Sexp::List& list, MuMsg* msg)
{
/* some mailing lists do not set the reply-to; see pull #1278. So for
* those cases, check the List-Post address and use that instead */
GMatchInfo* minfo;
GRegex* rx;
const char* list_post;
list_post = mu_msg_get_header(msg, "List-Post");
if (!list_post)
return;
rx = g_regex_new("<?mailto:([a-z0-9!@#$%&'*+-/=?^_`{|}~]+)>?",
G_REGEX_CASELESS,
(GRegexMatchFlags)0,
NULL);
g_return_if_fail(rx);
if (g_regex_match(rx, list_post, (GRegexMatchFlags)0, &minfo)) {
auto address = (char*)g_match_info_fetch(minfo, 1);
list.add_prop(":list-post", make_contact_sexp(Contact{address}));
g_free(address);
}
g_match_info_free(minfo);
g_regex_unref(rx);
}
static void
add_contacts(Sexp::List& list, MuMsg* msg)
{
using ContactPair = std::pair<Field::Id, std::string_view>;
constexpr std::array<ContactPair, 4> contact_types = {{
{ Field::Id::From, ":from" },
{ Field::Id::To, ":to" },
{ Field::Id::Cc, ":cc" },
// { Field::Id::ReplyTo, ":reply-to" },
{ Field::Id::Bcc, ":bcc" },
}};
for (auto&& contact_type : contact_types) {
const auto contacts{mu_msg_get_contacts(msg, contact_type.first)};
if (contacts.empty())
continue;
Sexp::List c_list;
for (auto&& contact: contacts)
c_list.add(make_contact_sexp(contact));
list.add_prop(std::string{contact_type.second},
Sexp::make_list(std::move(c_list)));
}
}
static void
add_flags(Sexp::List& list, MuMsg* msg)
{
Sexp::List flaglist;
const auto flags{mu_msg_get_flags(msg)};
for (auto&& info: AllMessageFlagInfos)
if (any_of(flags & info.flag))
flaglist.add(Sexp::make_symbol_sv(info.name));
if (!flaglist.empty())
list.add_prop(":flags", Sexp::make_list(std::move(flaglist)));
}
static char*
get_temp_file(MuMsg* msg, MuMsgOptions opts, unsigned index)
{
char* path;
GError* err;
err = NULL;
path = mu_msg_part_get_cache_path(msg, opts, index, &err);
if (!path)
goto errexit;
if (!mu_msg_part_save(msg, opts, path, index, &err))
goto errexit;
return path;
errexit:
g_warning("failed to save mime part: %s",
err->message ? err->message : "something went wrong");
g_clear_error(&err);
g_free(path);
return NULL;
}
static gchar*
get_temp_file_maybe(MuMsg* msg, MuMsgPart* part, MuMsgOptions opts)
{
opts = (MuMsgOptions)((int)opts | (int)MU_MSG_OPTION_USE_EXISTING);
if (!(opts & MU_MSG_OPTION_EXTRACT_IMAGES) || g_ascii_strcasecmp(part->type, "image") != 0)
return NULL;
else
return get_temp_file(msg, opts, part->index);
}
struct PartInfo {
Sexp::List parts;
MuMsgOptions opts;
};
static void
sig_verdict(Sexp::List& partlist, MuMsgPart* mpart)
{
MuMsgPartSigStatusReport* report = mpart->sig_status_report;
if (!report)
return;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (report->verdict) {
case MU_MSG_PART_SIG_STATUS_GOOD:
partlist.add_prop(":signature", Sexp::make_symbol("verified"));
break;
case MU_MSG_PART_SIG_STATUS_BAD:
partlist.add_prop(":signature", Sexp::make_symbol("bad"));
break;
case MU_MSG_PART_SIG_STATUS_ERROR:
partlist.add_prop(":signature", Sexp::make_symbol("unverified"));
break;
default: break;
}
#pragma GCC diagnostic pop
if (report->signers)
partlist.add_prop(":signers", Sexp::make_string(report->signers));
}
static void
dec_verdict(Sexp::List& partlist, MuMsgPart* mpart)
{
if (mpart->part_type & MU_MSG_PART_TYPE_DECRYPTED)
partlist.add_prop(":decryption", Sexp::make_symbol("succeeded"));
else if (mpart->part_type & MU_MSG_PART_TYPE_ENCRYPTED)
partlist.add_prop(":decryption", Sexp::make_symbol("failed"));
}
static Sexp
make_part_types(MuMsgPartType ptype)
{
struct PartTypes {
MuMsgPartType ptype;
const char* name;
} ptypes[] = {{MU_MSG_PART_TYPE_LEAF, "leaf"},
{MU_MSG_PART_TYPE_MESSAGE, "message"},
{MU_MSG_PART_TYPE_INLINE, "inline"},
{MU_MSG_PART_TYPE_ATTACHMENT, "attachment"},
{MU_MSG_PART_TYPE_SIGNED, "signed"},
{MU_MSG_PART_TYPE_ENCRYPTED, "encrypted"}};
Sexp::List list;
for (auto u = 0U; u != G_N_ELEMENTS(ptypes); ++u)
if (ptype & ptypes[u].ptype)
list.add(Sexp::make_symbol(ptypes[u].name));
return Sexp::make_list(std::move(list));
}
static void
each_part(MuMsg* msg, MuMsgPart* part, PartInfo* pinfo)
{
auto mimetype = format("%s/%s",
part->type ? part->type : "application",
part->subtype ? part->subtype : "octet-stream");
auto maybe_attach = Sexp::make_symbol(mu_msg_part_maybe_attachment(part) ? "t" : "nil");
Sexp::List partlist;
partlist.add_prop(":index", Sexp::make_number(part->index));
partlist.add_prop(":mime-type", Sexp::make_string(mimetype));
partlist.add_prop(":size", Sexp::make_number(part->size));
dec_verdict(partlist, part);
sig_verdict(partlist, part);
if (part->part_type)
partlist.add_prop(":type", make_part_types(part->part_type));
char* fname = mu_msg_part_get_filename(part, TRUE);
if (fname)
partlist.add_prop(":name", Sexp::make_string(fname));
g_free(fname);
if (mu_msg_part_maybe_attachment(part))
partlist.add_prop(":attachment", Sexp::make_symbol("t"));
const auto cid{mu_msg_part_get_content_id(part)};
if (cid)
partlist.add_prop(":cid", Sexp::make_string(cid));
char* tempfile = get_temp_file_maybe(msg, part, pinfo->opts);
if (tempfile)
partlist.add_prop(":temp", Sexp::make_string(tempfile));
g_free(tempfile);
pinfo->parts.add(Sexp::make_list(std::move(partlist)));
}
static void
add_parts(Sexp::List& items, MuMsg* msg, MuMsgOptions opts)
{
PartInfo pinfo;
pinfo.opts = opts;
if (mu_msg_part_foreach(msg, opts, (MuMsgPartForeachFunc)each_part, &pinfo) &&
!pinfo.parts.empty())
items.add_prop(":parts", Sexp::make_list(std::move(pinfo.parts)));
}
static void
add_message_file_parts(Sexp::List& items, MuMsg* msg, MuMsgOptions opts)
{
GError* err{NULL};
if (!mu_msg_load_msg_file(msg, &err)) {
g_warning("failed to load message file: %s",
err ? err->message : "some error occurred");
g_clear_error(&err);
return;
}
add_parts(items, msg, opts);
add_contacts(items, msg);
/* add the user-agent / x-mailer */
auto str = mu_msg_get_header(msg, "User-Agent");
if (!str)
str = mu_msg_get_header(msg, "X-Mailer");
add_prop_nonempty(items, ":user-agent", str);
add_prop_nonempty(items,
":body-txt-params",
mu_msg_get_body_text_content_type_parameters(msg, opts));
add_prop_nonempty(items, ":body-txt", mu_msg_get_body_text(msg, opts));
add_prop_nonempty(items, ":body-html", mu_msg_get_body_html(msg, opts));
}
static void
add_date_and_size(Sexp::List& items, MuMsg* msg)
{
auto t = mu_msg_get_date(msg);
if (t == (time_t)-1) /* invalid date? */
t = 0;
Sexp::List dlist;
dlist.add(Sexp::make_number((unsigned)(t >> 16)));
dlist.add(Sexp::make_number((unsigned)(t & 0xffff)));
dlist.add(Sexp::make_number(0));
items.add_prop(":date", Sexp::make_list(std::move(dlist)));
auto s = mu_msg_get_size(msg);
if (s == (size_t)-1) /* invalid size? */
s = 0;
items.add_prop(":size", Sexp::make_number(s));
}
static void
add_tags(Sexp::List& items, MuMsg* msg)
{
Sexp::List taglist;
for (auto tags = mu_msg_get_tags(msg); tags; tags = g_slist_next(tags))
taglist.add(Sexp::make_string((const char*)tags->data));
if (!taglist.empty())
items.add_prop(":tags", Sexp::make_list(std::move(taglist)));
}
Mu::Sexp::List
Mu::msg_to_sexp_list(MuMsg* msg, unsigned docid, MuMsgOptions opts)
{
g_return_val_if_fail(msg, Sexp::List());
g_return_val_if_fail(
!((opts & MU_MSG_OPTION_HEADERS_ONLY) && (opts & MU_MSG_OPTION_EXTRACT_IMAGES)),
Sexp::List());
Sexp::List items;
if (docid != 0)
items.add_prop(":docid", Sexp::make_number(docid));
add_prop_nonempty(items, ":subject", mu_msg_get_subject(msg));
add_prop_nonempty(items, ":message-id", mu_msg_get_msgid(msg));
add_prop_nonempty(items, ":mailing-list", mu_msg_get_mailing_list(msg));
add_prop_nonempty(items, ":path", mu_msg_get_path(msg));
add_prop_nonempty(items, ":maildir", mu_msg_get_maildir(msg));
items.add_prop(":priority",
Sexp::make_symbol_sv(priority_name(mu_msg_get_prio(msg))));
/* in the no-headers-only case (see below) we get a more complete list of contacts, so no
* need to get them here if that's the case */
if (opts & MU_MSG_OPTION_HEADERS_ONLY) {
add_contacts(items, msg);
add_list_post(items, msg);
}
add_prop_nonempty(items, ":references", mu_msg_get_references(msg));
add_prop_nonempty(items, ":in-reply-to", mu_msg_get_header(msg, "In-Reply-To"));
add_date_and_size(items, msg);
add_flags(items, msg);
add_tags(items, msg);
/* headers are retrieved from the database, views from the
* message file file attr things can only be gotten from the
* file (ie., mu view), not from the database (mu find). */
if (!(opts & MU_MSG_OPTION_HEADERS_ONLY))
add_message_file_parts(items, msg, opts);
return items;
}
Mu::Sexp
Mu::msg_to_sexp(MuMsg* msg, unsigned docid, MuMsgOptions opts)
{
return Sexp::make_list(msg_to_sexp_list(msg, docid, opts));
}

View File

@ -1,782 +0,0 @@
/*
** Copyright (C) 2008-2020 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 of the License, 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 <functional>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <gmime/gmime.h>
#include <vector>
#include <array>
#include "gmime/gmime-message.h"
#include "mu-msg-priv.hh" /* include before mu-msg.h */
#include "mu-msg.hh"
#include "utils/mu-str.h"
#include "mu-maildir.hh"
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. */
static gboolean _gmime_initialized = FALSE;
static void
gmime_init(void)
{
g_return_if_fail(!_gmime_initialized);
g_mime_init();
_gmime_initialized = TRUE;
}
static void
gmime_uninit(void)
{
g_return_if_fail(_gmime_initialized);
g_mime_shutdown();
_gmime_initialized = FALSE;
}
static MuMsg*
msg_new(void)
{
MuMsg* self;
self = g_new0(MuMsg, 1);
self->_refcount = 1;
return self;
}
MuMsg*
Mu::mu_msg_new_from_file(const char* path, const char* mdir, GError** err)
{
MuMsg* self;
MuMsgFile* msgfile;
gint64 start;
g_return_val_if_fail(path, NULL);
start = g_get_monotonic_time();
if (G_UNLIKELY(!_gmime_initialized)) {
gmime_init();
atexit(gmime_uninit);
}
msgfile = mu_msg_file_new(path, mdir, err);
if (!msgfile)
return NULL;
self = msg_new();
self->_file = msgfile;
g_debug("created message from %s in %" G_GINT64_FORMAT " μs",
path,
g_get_monotonic_time() - start);
return self;
}
MuMsg*
Mu::mu_msg_new_from_doc(XapianDocument* doc, GError** err)
{
MuMsg* self;
MuMsgDoc* msgdoc;
g_return_val_if_fail(doc, NULL);
if (G_UNLIKELY(!_gmime_initialized)) {
gmime_init();
atexit(gmime_uninit);
}
msgdoc = mu_msg_doc_new(doc, err);
if (!msgdoc)
return NULL;
self = msg_new();
self->_doc = msgdoc;
return self;
}
static void
mu_msg_destroy(MuMsg* self)
{
if (!self)
return;
mu_msg_file_destroy(self->_file);
mu_msg_doc_destroy(self->_doc);
{ /* cleanup the strings / lists we stored */
mu_str_free_list(self->_free_later_str);
for (auto cur = self->_free_later_lst; cur; cur = g_slist_next(cur))
g_slist_free_full((GSList*)cur->data, g_free);
g_slist_free(self->_free_later_lst);
}
g_free(self);
}
MuMsg*
Mu::mu_msg_ref(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
++self->_refcount;
return self;
}
void
Mu::mu_msg_unref(MuMsg* self)
{
g_return_if_fail(self);
g_return_if_fail(self->_refcount >= 1);
if (--self->_refcount == 0)
mu_msg_destroy(self);
}
static const gchar*
free_later_str(MuMsg* self, gchar* str)
{
if (str)
self->_free_later_str = g_slist_prepend(self->_free_later_str, str);
return str;
}
static const GSList*
free_later_lst(MuMsg* self, GSList* lst)
{
if (lst)
self->_free_later_lst = g_slist_prepend(self->_free_later_lst, lst);
return lst;
}
/* use this instead of mu_msg_get_path so we don't get into infinite
* regress...*/
static const char*
get_path(MuMsg* self)
{
char* val;
gboolean do_free;
do_free = TRUE;
val = NULL;
if (self->_doc)
val = mu_msg_doc_get_str_field(self->_doc, Field::Id::Path);
/* not in the cache yet? try to get it from the file backend,
* in case we are using that */
if (!val && self->_file)
val = mu_msg_file_get_str_field(self->_file, Field::Id::Path, &do_free);
/* shouldn't happen */
if (!val)
g_warning("%s: message without path?!", __func__);
return free_later_str(self, val);
}
/* for some data, we need to read the message file from disk */
gboolean
Mu::mu_msg_load_msg_file(MuMsg* self, GError** err)
{
const char* path;
g_return_val_if_fail(self, FALSE);
if (self->_file)
return TRUE; /* nothing to do */
if (!(path = get_path(self))) {
mu_util_g_set_error(err, MU_ERROR_INTERNAL, "cannot get path for message");
return FALSE;
}
self->_file = mu_msg_file_new(path, NULL, err);
return (self->_file != NULL);
}
void
Mu::mu_msg_unload_msg_file(MuMsg* msg)
{
g_return_if_fail(msg);
mu_msg_file_destroy(msg->_file);
msg->_file = NULL;
}
static const GSList*
get_str_list_field(MuMsg* self, Field::Id field_id)
{
GSList* val;
val = NULL;
if (self->_doc &&
any_of(field_from_id(field_id).flags & Field::Flag::Value))
val = mu_msg_doc_get_str_list_field(self->_doc, field_id);
else if (any_of(field_from_id(field_id).flags & Field::Flag::GMime)) {
/* if we don't have a file object yet, we need to
* create it from the file on disk */
if (!mu_msg_load_msg_file(self, NULL))
return NULL;
val = mu_msg_file_get_str_list_field(self->_file, field_id);
}
return free_later_lst(self, val);
}
static const char*
get_str_field(MuMsg* self, Field::Id field_id)
{
char* val;
gboolean do_free;
do_free = TRUE;
val = NULL;
if (self->_doc &&
any_of(field_from_id(field_id).flags & Field::Flag::Value))
val = mu_msg_doc_get_str_field(self->_doc, field_id);
else if (any_of(field_from_id(field_id).flags & Field::Flag::GMime)) {
/* if we don't have a file object yet, we need to
* create it from the file on disk */
if (!mu_msg_load_msg_file(self, NULL))
return NULL;
val = mu_msg_file_get_str_field(self->_file, field_id, &do_free);
} else
val = NULL;
return do_free ? free_later_str(self, val) : val;
}
static gint64
get_num_field(MuMsg* self, Field::Id field_id)
{
if (self->_doc &&
any_of(field_from_id(field_id).flags & Field::Flag::Value))
return mu_msg_doc_get_num_field(self->_doc, field_id);
/* if we don't have a file object yet, we need to
* create it from the file on disk */
if (!mu_msg_load_msg_file(self, NULL))
return -1;
return mu_msg_file_get_num_field(self->_file, field_id);
}
const char*
Mu::mu_msg_get_header(MuMsg* self, const char* header)
{
GError* err;
g_return_val_if_fail(self, NULL);
g_return_val_if_fail(header, NULL);
/* if we don't have a file object yet, we need to
* create it from the file on disk */
err = NULL;
if (!mu_msg_load_msg_file(self, &err)) {
g_warning("failed to load message file: %s",
err ? err->message : "something went wrong");
return NULL;
}
return free_later_str(self, mu_msg_file_get_header(self->_file, header));
}
time_t
Mu::mu_msg_get_timestamp(MuMsg* self)
{
const char* path;
struct stat statbuf;
g_return_val_if_fail(self, 0);
if (self->_file)
return self->_file->_timestamp;
path = mu_msg_get_path(self);
if (!path || stat(path, &statbuf) < 0)
return 0;
return statbuf.st_mtime;
}
const char*
Mu::mu_msg_get_path(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::Path);
}
const char*
Mu::mu_msg_get_subject(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::Subject);
}
const char*
Mu::mu_msg_get_msgid(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::MessageId);
}
const char*
Mu::mu_msg_get_mailing_list(MuMsg* self)
{
const char* ml;
char* decml;
g_return_val_if_fail(self, NULL);
ml = get_str_field(self, Field::Id::MailingList);
if (!ml)
return NULL;
decml = g_mime_utils_header_decode_text(NULL, ml);
if (!decml)
return NULL;
return free_later_str(self, decml);
}
const char*
Mu::mu_msg_get_maildir(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::Maildir);
}
const char*
Mu::mu_msg_get_from(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::From);
}
const char*
Mu::mu_msg_get_to(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::To);
}
const char*
Mu::mu_msg_get_cc(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::Cc);
}
const char*
Mu::mu_msg_get_bcc(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, Field::Id::Bcc);
}
time_t
Mu::mu_msg_get_date(MuMsg* self)
{
g_return_val_if_fail(self, (time_t)-1);
return (time_t)get_num_field(self, Field::Id::Date);
}
Flags
Mu::mu_msg_get_flags(MuMsg* self)
{
g_return_val_if_fail(self, Flags::None);
return static_cast<Flags>(get_num_field(self, Field::Id::Flags));
}
size_t
Mu::mu_msg_get_size(MuMsg* self)
{
g_return_val_if_fail(self, (size_t)-1);
return (size_t)get_num_field(self, Field::Id::Size);
}
Mu::Priority
Mu::mu_msg_get_prio(MuMsg* self)
{
g_return_val_if_fail(self, Priority{});
return priority_from_char(
static_cast<char>(get_num_field(self, Field::Id::Priority)));
}
struct _BodyData {
GString* gstr;
gboolean want_html;
};
typedef struct _BodyData BodyData;
static void
accumulate_body(MuMsg* msg, MuMsgPart* mpart, BodyData* bdata)
{
char* txt;
GMimePart* mimepart;
gboolean has_err, is_plain, is_html;
if (!GMIME_IS_PART(mpart->data))
return;
if (mpart->part_type & MU_MSG_PART_TYPE_ATTACHMENT)
return;
mimepart = (GMimePart*)mpart->data;
is_html = mpart->part_type & MU_MSG_PART_TYPE_TEXT_HTML;
is_plain = mpart->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN;
txt = NULL;
has_err = TRUE;
if ((bdata->want_html && is_html) || (!bdata->want_html && is_plain))
txt = mu_msg_mime_part_to_string(mimepart, &has_err);
if (!has_err && txt)
bdata->gstr = g_string_append(bdata->gstr, txt);
g_free(txt);
}
static char*
get_body(MuMsg* self, MuMsgOptions opts, gboolean want_html)
{
BodyData bdata;
bdata.want_html = want_html;
bdata.gstr = g_string_sized_new(4096);
/* wipe out some irrelevant options */
opts &= ~MU_MSG_OPTION_VERIFY;
opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES;
mu_msg_part_foreach(self, opts, (MuMsgPartForeachFunc)accumulate_body, &bdata);
if (bdata.gstr->len == 0) {
g_string_free(bdata.gstr, TRUE);
return NULL;
} else
return g_string_free(bdata.gstr, FALSE);
}
typedef struct {
GMimeContentType* ctype;
gboolean want_html;
} ContentTypeData;
static void
find_content_type(MuMsg* msg, MuMsgPart* mpart, ContentTypeData* cdata)
{
GMimePart* wanted;
if (!GMIME_IS_PART(mpart->data))
return;
/* text-like attachments are included when in text-mode */
if (!cdata->want_html && (mpart->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN))
wanted = (GMimePart*)mpart->data;
else if (!(mpart->part_type & MU_MSG_PART_TYPE_ATTACHMENT) && cdata->want_html &&
(mpart->part_type & MU_MSG_PART_TYPE_TEXT_HTML))
wanted = (GMimePart*)mpart->data;
else
wanted = NULL;
if (wanted)
cdata->ctype = g_mime_object_get_content_type(GMIME_OBJECT(wanted));
}
static const GSList*
get_content_type_parameters(MuMsg* self, MuMsgOptions opts, gboolean want_html)
{
ContentTypeData cdata;
cdata.want_html = want_html;
cdata.ctype = NULL;
/* wipe out some irrelevant options */
opts &= ~MU_MSG_OPTION_VERIFY;
opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES;
mu_msg_part_foreach(self, opts, (MuMsgPartForeachFunc)find_content_type, &cdata);
if (cdata.ctype) {
GSList* gslist;
GMimeParamList* paramlist;
const GMimeParam* param;
int i, len;
gslist = NULL;
paramlist = g_mime_content_type_get_parameters(cdata.ctype);
len = g_mime_param_list_length(paramlist);
for (i = 0; i < len; ++i) {
param = g_mime_param_list_get_parameter_at(paramlist, i);
gslist = g_slist_prepend(gslist, g_strdup(param->name));
gslist = g_slist_prepend(gslist, g_strdup(param->value));
}
return free_later_lst(self, g_slist_reverse(gslist));
}
return NULL;
}
const GSList*
Mu::mu_msg_get_body_text_content_type_parameters(MuMsg* self, MuMsgOptions opts)
{
g_return_val_if_fail(self, NULL);
return get_content_type_parameters(self, opts, FALSE);
}
const char*
Mu::mu_msg_get_body_html(MuMsg* self, MuMsgOptions opts)
{
g_return_val_if_fail(self, NULL);
return free_later_str(self, get_body(self, opts, TRUE));
}
const char*
Mu::mu_msg_get_body_text(MuMsg* self, MuMsgOptions opts)
{
g_return_val_if_fail(self, NULL);
return free_later_str(self, get_body(self, opts, FALSE));
}
const GSList*
Mu::mu_msg_get_references(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_list_field(self, Field::Id::References);
}
const GSList*
Mu::mu_msg_get_tags(MuMsg* self)
{
g_return_val_if_fail(self, NULL);
return get_str_list_field(self, Field::Id::Tags);
}
const char*
Mu::mu_msg_get_field_string(MuMsg* self, Field::Id field_id)
{
g_return_val_if_fail(self, NULL);
return get_str_field(self, field_id);
}
const GSList*
Mu::mu_msg_get_field_string_list(MuMsg* self, Field::Id field_id)
{
g_return_val_if_fail(self, NULL);
return get_str_list_field(self, field_id);
}
gint64
Mu::mu_msg_get_field_numeric(MuMsg* self, Field::Id field_id)
{
g_return_val_if_fail(self, -1);
return get_num_field(self, field_id);
}
static Mu::Contacts
get_all_contacts(MuMsg *self)
{
Contacts contacts;
field_for_each([&](const auto& field){
if (!field.is_contact())
return;
auto type_contacts{mu_msg_get_contacts(self, field.id)};
contacts.reserve(contacts.size() + type_contacts.size());
contacts.insert(contacts.end(), type_contacts.begin(),
type_contacts.end());
});
return contacts;
}
Mu::Contacts
Mu::mu_msg_get_contacts(MuMsg *self, Option<Field::Id> field_id)
{
typedef const char*(*AddressFunc)(MuMsg*);
using AddressInfo = std::pair<GMimeAddressType, AddressFunc>;
g_return_val_if_fail(self, Contacts{});
g_return_val_if_fail(!field_id || field_from_id(*field_id).is_contact(),
Contacts{});
if (!field_id)
return get_all_contacts(self);
const auto info = std::invoke([&]()->AddressInfo {
switch (*field_id) {
case Field::Id::From:
return { GMIME_ADDRESS_TYPE_FROM, mu_msg_get_from };
case Field::Id::To:
return { GMIME_ADDRESS_TYPE_TO, mu_msg_get_to };
case Field::Id::Cc:
return { GMIME_ADDRESS_TYPE_CC, mu_msg_get_cc };
case Field::Id::Bcc:
return { GMIME_ADDRESS_TYPE_BCC, mu_msg_get_bcc };
default:
throw std::logic_error("bug");
}
});
const auto mdate{mu_msg_get_date(self)};
if (self->_file) {
if (auto&& lst{g_mime_message_get_addresses(
self->_file->_mime_msg, info.first)}; lst)
return make_contacts(lst, *field_id, mdate);
} else if (info.second) {
if (auto&& lst_str{info.second(self)}; lst_str)
return make_contacts(lst_str, *field_id, mdate);
}
return {};
}
gboolean
Mu::mu_msg_is_readable(MuMsg* self)
{
g_return_val_if_fail(self, FALSE);
return access(mu_msg_get_path(self), R_OK) == 0 ? TRUE : FALSE;
}
/*
* move a msg to another maildir, trying to maintain 'integrity',
* ie. msg in 'new/' will go to new/, one in cur/ goes to cur/. be
* super-paranoid here...
*/
bool
Mu::mu_msg_move_to_maildir(MuMsg* self,
const std::string& root_maildir_path,
const std::string& target_maildir,
Flags flags,
bool ignore_dups,
bool new_name,
GError** err)
{
g_return_val_if_fail(self, false);
const auto srcpath{mu_msg_get_path(self)};
const auto dstpath{mu_maildir_determine_target(srcpath,
root_maildir_path,
target_maildir,
flags,
new_name)};
if (!dstpath)
return false;
if (!mu_maildir_move_message(srcpath, *dstpath, ignore_dups))
return false;
/* clear the old backends */
mu_msg_doc_destroy(self->_doc);
self->_doc = NULL;
mu_msg_file_destroy(self->_file);
/* and create a new one */
self->_file = mu_msg_file_new(dstpath->c_str(), target_maildir.c_str(), err);
return !!self->_file;
}
static void
cleanup_contact(char* contact)
{
char *c, *c2;
/* replace "'<> with space */
for (c2 = contact; *c2; ++c2)
if (*c2 == '"' || *c2 == '\'' || *c2 == '<' || *c2 == '>')
*c2 = ' ';
/* remove everything between '()' if it's after the 5th pos;
* good to cleanup corporate contact address spam... */
c = g_strstr_len(contact, -1, "(");
if (c && c - contact > 5)
*c = '\0';
g_strstrip(contact);
}
/* this is still somewhat simplistic... */
const char*
Mu::mu_str_display_contact_s(const char* str)
{
static gchar contact[255];
gchar * c, *c2;
str = str ? str : "";
g_strlcpy(contact, str, sizeof(contact));
/* we check for '<', so we can strip out the address stuff in
* e.g. 'Hello World <hello@world.xx>, but only if there is
* something alphanumeric before the <
*/
c = g_strstr_len(contact, -1, "<");
if (c != NULL) {
for (c2 = contact; c2 < c && !(isalnum(*c2)); ++c2)
;
if (c2 != c) /* apparently, there was something,
* so we can remove the <... part*/
*c = '\0';
}
cleanup_contact(contact);
return contact;
}
char*
Mu::mu_str_display_contact(const char* str)
{
g_return_val_if_fail(str, NULL);
return g_strdup(mu_str_display_contact_s(str));
}

View File

@ -1,499 +0,0 @@
/*
** Copyright (C) 2010-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 of the License, 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_MSG_HH__
#define MU_MSG_HH__
#include <utils/mu-result.hh>
#include <message/mu-message.hh>
#include <utils/mu-util.h>
#include <utils/mu-utils.hh>
#include <utils/mu-option.hh>
#include <utils/mu-sexp.hh>
namespace Mu {
struct MuMsg;
/* options for various functions */
enum MuMsgOptions {
MU_MSG_OPTION_NONE = 0,
/* 1 << 0 is still free! */
/* for -> sexp conversion */
MU_MSG_OPTION_HEADERS_ONLY = 1 << 1,
MU_MSG_OPTION_EXTRACT_IMAGES = 1 << 2,
/* below options are for checking signatures; only effective
* if mu was built with crypto support */
MU_MSG_OPTION_VERIFY = 1 << 4,
MU_MSG_OPTION_AUTO_RETRIEVE = 1 << 5,
MU_MSG_OPTION_USE_AGENT = 1 << 6,
/* MU_MSG_OPTION_USE_PKCS7 = 1 << 7, /\* gpg is the default *\/ */
/* get password from console if needed */
MU_MSG_OPTION_CONSOLE_PASSWORD = 1 << 7,
MU_MSG_OPTION_DECRYPT = 1 << 8,
/* misc */
MU_MSG_OPTION_OVERWRITE = 1 << 9,
MU_MSG_OPTION_USE_EXISTING = 1 << 10,
/* recurse into submessages */
MU_MSG_OPTION_RECURSE_RFC822 = 1 << 11
};
MU_ENABLE_BITOPS(MuMsgOptions);
/**
* create a new MuMsg* instance which parses a message and provides
* read access to its properties; call mu_msg_unref when done with it.
*
* @param path full path to an email message file
* @param mdir the maildir for this message; ie, if the path is
* ~/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.
* @param err receive error information (MU_ERROR_FILE or
* MU_ERROR_GMIME), or NULL. There will only be err info if the
* function returns NULL
*
* @return a new MuMsg instance or NULL in case of error; call
* mu_msg_unref when done with this message
*/
MuMsg* mu_msg_new_from_file(const char* filepath,
const char* maildir,
GError** err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/**
* create a new MuMsg* instance based on a Xapian::Document
*
* @param store a MuStore ptr
* @param doc a ptr to a Xapian::Document (but cast to XapianDocument,
* because this is C not C++). MuMsg takes _ownership_ of this pointer;
* don't touch it afterwards
* @param err receive error information, or NULL. There
* will only be err info if the function returns NULL
*
* @return a new MuMsg instance or NULL in case of error; call
* mu_msg_unref when done with this message
*/
MuMsg* mu_msg_new_from_doc(XapianDocument* doc,
GError** err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/**
* if we don't have a message file yet (because this message is
* database-backed), load it.
*
* @param msg a MuMsg
* @param err receives error information
*
* @return TRUE if this succeeded, FALSE in case of error
*/
gboolean mu_msg_load_msg_file(MuMsg* msg, GError** err);
/**
* close the file-backend, if any; this function is for the use case
* where you have a large amount of messages where you need some
* file-backed field (body or attachments). If you don't close the
* file-backend after retrieving the desired field, you'd quickly run
* out of file descriptors. If this message does not have a
* file-backend, do nothing.
*
* @param msg a message object
*/
void mu_msg_unload_msg_file(MuMsg* msg);
/**
* increase the reference count for this message
*
* @param msg a message
*
* @return the message with its reference count increased, or NULL in
* case of error.
*/
MuMsg* mu_msg_ref(MuMsg* msg);
/**
* decrease the reference count for this message. if the reference
* count reaches 0, the message will be destroyed.
*
* @param msg a message
*/
void mu_msg_unref(MuMsg* msg);
/**
* cache the values from the backend (file or db), so we don't the
* backend anymore
*
* @param self a message
*/
void mu_msg_cache_values(MuMsg* self);
/**
* get the plain text body of this message
*
* @param msg a valid MuMsg* instance
* @param opts options for getting the body
*
* @return the plain text body or NULL in case of error or if there is no
* such body. the returned string should *not* be modified or freed.
* The returned data is in UTF8 or NULL.
*/
const char* mu_msg_get_body_text(MuMsg* msg, MuMsgOptions opts);
/**
* get the content type parameters for the text body part
*
* @param msg a valid MuMsg* instance
* @param opts options for getting the body
*
* @return the value of the requested body part content type parameter, or
* NULL in case of error or if there is no such body. the returned string
* should *not* be modified or freed. The returned data is in UTF8 or NULL.
*/
const GSList* mu_msg_get_body_text_content_type_parameters(MuMsg* self, MuMsgOptions opts);
/**
* get the html body of this message
*
* @param msg a valid MuMsg* instance
* @param opts options for getting the body
*
* @return the html body or NULL in case of error or if there is no
* such body. the returned string should *not* be modified or freed.
*/
const char* mu_msg_get_body_html(MuMsg* msgMu, MuMsgOptions opts);
/**
* get the sender (From:) of this message
*
* @param msg a valid MuMsg* instance
*
* @return the sender of this Message or NULL in case of error or if there
* is no sender. the returned string should *not* be modified or freed.
*/
const char* mu_msg_get_from(MuMsg* msg);
/**
* get the recipients (To:) of this message
*
* @param msg a valid MuMsg* instance
*
* @return the sender of this Message or NULL in case of error or if there
* are no recipients. the returned string should *not* be modified or freed.
*/
const char* mu_msg_get_to(MuMsg* msg);
/**
* get the carbon-copy recipients (Cc:) of this message
*
* @param msg a valid MuMsg* instance
*
* @return the Cc: recipients of this Message or NULL in case of error or if
* there are no such recipients. the returned string should *not* be modified
* or freed.
*/
const char* mu_msg_get_cc(MuMsg* msg);
/**
* get the blind carbon-copy recipients (Bcc:) of this message; this
* field usually only appears in outgoing messages
*
* @param msg a valid MuMsg* instance
*
* @return the Bcc: recipients of this Message or NULL in case of
* error or if there are no such recipients. the returned string
* should *not* be modified or freed.
*/
const char* mu_msg_get_bcc(MuMsg* msg);
/**
* get the file system path of this message
*
* @param msg a valid MuMsg* instance
*
* @return the path of this Message or NULL in case of error.
* the returned string should *not* be modified or freed.
*/
const char* mu_msg_get_path(MuMsg* msg);
/**
* get the maildir this message lives in; ie, if the path is
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar
*
* @param msg a valid MuMsg* instance
*
* @return the maildir requested or NULL in case of error. The returned
* string should *not* be modified or freed.
*/
const char* mu_msg_get_maildir(MuMsg* msg);
/**
* get the subject of this message
*
* @param msg a valid MuMsg* instance
*
* @return the subject of this Message or NULL in case of error or if there
* is no subject. the returned string should *not* be modified or freed.
*/
const char* mu_msg_get_subject(MuMsg* msg);
/**
* get the Message-Id of this message
*
* @param msg a valid MuMsg* instance
*
* @return the Message-Id of this message (without the enclosing <>),
* or a fake message-id for messages that don't have them, or NULL in
* case of error.
*/
const char* mu_msg_get_msgid(MuMsg* msg);
/**
* get the mailing list for a message, i.e. the mailing-list
* identifier in the List-Id header.
*
* @param msg a valid MuMsg* instance
*
* @return the mailing list id for this message (without the enclosing <>)
* or NULL in case of error or if there is none. the returned string
* should *not* be modified or freed.
*/
const char* mu_msg_get_mailing_list(MuMsg* msg);
/**
* get the message date/time (the Date: field) as time_t, using UTC
*
* @param msg a valid MuMsg* instance
*
* @return message date/time or 0 in case of error or if there
* is no such header.
*/
time_t mu_msg_get_date(MuMsg* msg);
/**
* get the flags for this message
*
* @param msg valid MuMsg* instance
*
* @return the file/content flags as logically OR'd #Mu::Flags.
* Non-standard flags are ignored.
*/
Flags mu_msg_get_flags(MuMsg* msg);
/**
* get the file size in bytes of this message
*
* @param msg a valid MuMsg* instance
*
* @return the filesize
*/
size_t mu_msg_get_size(MuMsg* msg);
/**
* get some field value as string
*
* @param msg a valid MuMsg instance
* @param field the field to retrieve; it must be a string-typed field
*
* @return a string that should not be freed
*/
const char* mu_msg_get_field_string(MuMsg* msg, Field::Id mfid);
/**
* get some field value as string-list
*
* @param msg a valid MuMsg instance
* @param field the field to retrieve; it must be a string-list-typed field
*
* @return a list that should not be freed
*/
const GSList* mu_msg_get_field_string_list(MuMsg* self, Field::Id mfid);
/**
* get some field value as string
*
* @param msg a valid MuMsg instance
* @param field the field to retrieve; it must be a numeric field
*
* @return a string that should not be freed
*/
gint64 mu_msg_get_field_numeric(MuMsg* msg, Field::Id mfid);
/**
* get the message priority for this message. The X-Priority, X-MSMailPriority,
* Importance and Precedence header are checked, in that order. if no known or
* explicit priority is set, MessagePriority::Id::Normal is assumed
*
* @param msg a valid MuMsg* instance
*
* @return the message priority
*/
Priority mu_msg_get_prio(MuMsg* msg);
/**
* get the timestamp (mtime) for the file containing this message
*
* @param msg a valid MuMsg* instance
*
* @return the timestamp or 0 in case of error
*/
time_t mu_msg_get_timestamp(MuMsg* msg);
/**
* get a specific header from the message. This value will _not_ be
* cached
*
* @param self a MuMsg instance
* @param header a specific header (like 'X-Mailer' or 'Organization')
*
* @return a header string which is valid as long as this MuMsg is
*/
const char* mu_msg_get_header(MuMsg* self, const char* header);
/**
* get the list of references (consisting of both the References and
* In-Reply-To fields), with the oldest first and the direct parent as
* the last one. Note, any reference (message-id) will appear at most
* once, duplicates are filtered out.
*
* @param msg a valid MuMsg
*
* @return a list with the references for this msg. Don't modify/free
*/
const GSList* mu_msg_get_references(MuMsg* msg);
/**
* get the list of tags (ie., X-Label)
*
* @param msg a valid MuMsg
*
* @return a list with the tags for this msg. Don't modify/free
*/
const GSList* mu_msg_get_tags(MuMsg* self);
/**
* compare two messages for sorting
*
* @param m1 a message
* @param m2 another message
* @param mfid the message to use for the comparison
*
* @return negative if m1 is smaller, positive if m1 is smaller, 0 if
* they are equal
*/
int mu_msg_cmp(MuMsg* m1, MuMsg* m2, Field::Id mfid);
/**
* check whether there there's a readable file behind this message
*
* @param self a MuMsg*
*
* @return TRUE if the message file is readable, FALSE otherwise
*/
gboolean mu_msg_is_readable(MuMsg* self);
/**
* move a message to another maildir; note that this does _not_ update
* the database
*
* @param msg a message with an existing file system path in an actual
* maildir
* @param root_maildir_path file system path for the root-maildir for this message
* e.g., /home/user/Maildir
* @param target_maildir the subdir where the message should go, relative to
* rootmaildir. e.g. "/archive"
* @param flags to set for the target (influences the filename, path)
* @param silently ignore the src=target case (return TRUE)
* @param new_name whether to create a new unique name, or keep the
* old one
* @param err receives error information
*
* @return TRUE if it worked, FALSE otherwise
*/
bool mu_msg_move_to_maildir(MuMsg* msg,
const std::string& root_maildir_path,
const std::string& target_maildir,
Flags flags,
bool ignore_dups,
bool new_name,
GError** err);
/**
* Get a sequence with contacts of the given type for this message.
*
* @param msg a valid MuMsg* instance
* @param field_id the contact field or none; if none get _all_ contact types.
*
* @return a sequence
*/
Mu::Contacts mu_msg_get_contacts (MuMsg *self,
Option<Field::Id> field_id={});
/**
* create a 'display contact' from an email header To/Cc/Bcc/From-type address
* ie., turn
* "Foo Bar" <foo@bar.com>
* into
* Foo Bar
* Note that this is based on some simple heuristics. Max length is 255 bytes.
*
* mu_str_display_contact_s returns a statically allocated
* buffer (ie, non-reentrant), while mu_str_display_contact
* returns a newly allocated string that you must free with g_free
* when done with it.
*
* @param str a 'contact str' (ie., what is in the To/Cc/Bcc/From
* fields), or NULL
*
* @return a newly allocated string with a display contact
*/
const char* mu_str_display_contact_s(const char* str) G_GNUC_CONST;
char* mu_str_display_contact(const char* str) G_GNUC_WARN_UNUSED_RESULT;
struct QueryMatch;
/**
* convert the msg to a Lisp symbolic expression (for further processing in
* e.g. emacs)
*
* @param msg a valid message
* @param docid the docid for this message, or 0
* @param opts, bitwise OR'ed;
* - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be
* obtained from the database (this is much faster if the MuMsg is
* database-backed, so no file needs to be opened)
* - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary
* files and include links to those in the sexp
* and for message parts:
* MU_MSG_OPTION_CHECK_SIGNATURES: check signatures
* MU_MSG_OPTION_AUTO_RETRIEVE_KEY: attempt to retrieve keys online
* MU_MSG_OPTION_USE_AGENT: attempt to use GPG-agent
* MU_MSG_OPTION_USE_PKCS7: attempt to use PKCS (instead of gpg)
*
* @return a Mu::Sexp or a Mu::Sexp::List representing the message.
*/
Mu::Sexp::List msg_to_sexp_list(MuMsg* msg, unsigned docid, MuMsgOptions ops);
Mu::Sexp msg_to_sexp(MuMsg* msg, unsigned docid, MuMsgOptions ops);
} // namespace Mu
#endif /*MU_MSG_HH__*/