mirror of https://github.com/djcb/mu.git
lib: remove the old mu-msg* code
Replaced by Mu::Message & friends
This commit is contained in:
parent
85a3ff71ae
commit
de07df77d3
|
@ -34,13 +34,7 @@ lib_mu=static_library(
|
||||||
'mu-store.cc',
|
'mu-store.cc',
|
||||||
'mu-tokenizer.cc',
|
'mu-tokenizer.cc',
|
||||||
'mu-xapian.cc',
|
'mu-xapian.cc',
|
||||||
'mu-maildir.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',
|
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
glib_dep,
|
glib_dep,
|
||||||
|
|
|
@ -22,8 +22,6 @@
|
||||||
#include "mu-message.hh"
|
#include "mu-message.hh"
|
||||||
#include "mu-query-results.hh"
|
#include "mu-query-results.hh"
|
||||||
#include "utils/mu-str.h"
|
#include "utils/mu-str.h"
|
||||||
#include "mu-msg.hh"
|
|
||||||
#include "mu-msg-part.hh"
|
|
||||||
#include "mu-maildir.hh"
|
#include "mu-maildir.hh"
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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__*/
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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__*/
|
|
1023
lib/mu-msg-part.cc
1023
lib/mu-msg-part.cc
File diff suppressed because it is too large
Load Diff
|
@ -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__*/
|
|
|
@ -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__*/
|
|
|
@ -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));
|
|
||||||
}
|
|
782
lib/mu-msg.cc
782
lib/mu-msg.cc
|
@ -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));
|
|
||||||
}
|
|
499
lib/mu-msg.hh
499
lib/mu-msg.hh
|
@ -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__*/
|
|
Loading…
Reference in New Issue