diff --git a/lib/meson.build b/lib/meson.build index 550e1f8c..469980e8 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -34,13 +34,7 @@ lib_mu=static_library( 'mu-store.cc', 'mu-tokenizer.cc', 'mu-xapian.cc', - 'mu-maildir.cc', - 'mu-msg-crypto.cc', - 'mu-msg-doc.cc', - 'mu-msg-file.cc', - 'mu-msg-part.cc', - 'mu-msg-sexp.cc', - 'mu-msg.cc', + 'mu-maildir.cc' ], dependencies: [ glib_dep, diff --git a/lib/message/mu-message-sexp.cc b/lib/message/mu-message-sexp.cc index 9d0a18bc..2da93a8b 100644 --- a/lib/message/mu-message-sexp.cc +++ b/lib/message/mu-message-sexp.cc @@ -22,8 +22,6 @@ #include "mu-message.hh" #include "mu-query-results.hh" #include "utils/mu-str.h" -#include "mu-msg.hh" -#include "mu-msg-part.hh" #include "mu-maildir.hh" #include "utils/mu-utils.hh" diff --git a/lib/mu-msg-crypto.cc b/lib/mu-msg-crypto.cc deleted file mode 100644 index 13493a3c..00000000 --- a/lib/mu-msg-crypto.cc +++ /dev/null @@ -1,343 +0,0 @@ -/* -** Copyright (C) 2012-2022 Dirk-Jan C. Binnema -** -** 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 -#include - -#include "gmime/gmime-signature.h" -#include "mu-msg.hh" -#include "mu-msg-priv.hh" -#include "mu-msg-part.hh" - -#include -#include - -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(-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(&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; -} diff --git a/lib/mu-msg-doc.cc b/lib/mu-msg-doc.cc deleted file mode 100644 index 6d2a522b..00000000 --- a/lib/mu-msg-doc.cc +++ /dev/null @@ -1,119 +0,0 @@ -/* -** Copyright (C) 2008-2020 Dirk-Jan C. Binnema -** -** 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 -#include -#include -#include -#include - -#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(strtol(s.c_str(), NULL, 10)); - else { - return static_cast(Xapian::sortable_unserialise(s)); - } - }, - (gint64)-1); -} diff --git a/lib/mu-msg-doc.hh b/lib/mu-msg-doc.hh deleted file mode 100644 index 17834baa..00000000 --- a/lib/mu-msg-doc.hh +++ /dev/null @@ -1,90 +0,0 @@ -/* -** Copyright (C) 2012-2022 Dirk-Jan C. Binnema -** -** 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 -#include -#include - -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__*/ diff --git a/lib/mu-msg-file.cc b/lib/mu-msg-file.cc deleted file mode 100644 index a3f14173..00000000 --- a/lib/mu-msg-file.cc +++ /dev/null @@ -1,850 +0,0 @@ -/* -** Copyright (C) 2012-2021 Dirk-Jan C. Binnema -** -** 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 - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#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 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); -} diff --git a/lib/mu-msg-file.hh b/lib/mu-msg-file.hh deleted file mode 100644 index 91507d24..00000000 --- a/lib/mu-msg-file.hh +++ /dev/null @@ -1,100 +0,0 @@ -/* -** Copyright (C) 2012-2020 Dirk-Jan C. Binnema -** -** 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__*/ diff --git a/lib/mu-msg-part.cc b/lib/mu-msg-part.cc deleted file mode 100644 index 551bf0f0..00000000 --- a/lib/mu-msg-part.cc +++ /dev/null @@ -1,1023 +0,0 @@ -/* -** Copyright (C) 2008-2020 Dirk-Jan C. Binnema -** -** 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 -#include - -#include "mu-msg.hh" -#include "utils/mu-util.h" -#include "utils/mu-str.h" -#include "mu-msg-priv.hh" -#include "mu-msg-part.hh" - -using namespace Mu; - -struct _DoData { - GMimeObject* mime_obj; - unsigned index; -}; -typedef struct _DoData DoData; - -static void -do_it_with_index(MuMsg* msg, MuMsgPart* part, DoData* ddata) -{ - if (ddata->mime_obj) - return; - - if (part->index == ddata->index) { - /* Add a reference to this object, this way if it is - * encrypted it will not be garbage collected before - * we are done with it. */ - g_object_ref(part->data); - ddata->mime_obj = (GMimeObject*)part->data; - } -} - -static GMimeObject* -get_mime_object_at_index(MuMsg* msg, MuMsgOptions opts, unsigned index) -{ - DoData ddata; - - ddata.mime_obj = NULL; - ddata.index = index; - - /* wipe out some irrelevant options */ - opts &= (MuMsgOptions)~MU_MSG_OPTION_VERIFY; - opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES; - - mu_msg_part_foreach(msg, opts, (MuMsgPartForeachFunc)do_it_with_index, &ddata); - - return ddata.mime_obj; -} - -typedef gboolean (*MuMsgPartMatchFunc)(MuMsgPart*, gpointer); -struct _MatchData { - MuMsgPartMatchFunc match_func; - gpointer user_data; - int index; -}; -typedef struct _MatchData MatchData; - -static void -check_match(MuMsg* msg, MuMsgPart* part, MatchData* mdata) -{ - if (mdata->index != -1) - return; - - if (mdata->match_func(part, mdata->user_data)) - mdata->index = part->index; -} - -static int -get_matching_part_index(MuMsg* msg, MuMsgOptions opts, MuMsgPartMatchFunc func, gpointer user_data) -{ - MatchData mdata; - - mdata.match_func = func; - mdata.user_data = user_data; - mdata.index = -1; - - mu_msg_part_foreach(msg, opts, (MuMsgPartForeachFunc)check_match, &mdata); - return mdata.index; -} - -static void -accumulate_text_message(MuMsg* msg, MuMsgPart* part, GString** gstrp) -{ - const gchar* str; - char* adrs; - GMimeMessage* mimemsg; - InternetAddressList* addresses; - - /* put sender, recipients and subject in the string, so they - * can be indexed as well */ - mimemsg = GMIME_MESSAGE(part->data); - addresses = g_mime_message_get_addresses(mimemsg, GMIME_ADDRESS_TYPE_FROM); - adrs = internet_address_list_to_string(addresses, NULL, FALSE); - - g_string_append_printf(*gstrp, "%s%s", adrs ? adrs : "", adrs ? "\n" : ""); - g_free(adrs); - - str = g_mime_message_get_subject(mimemsg); - g_string_append_printf(*gstrp, "%s%s", str ? str : "", str ? "\n" : ""); - - addresses = g_mime_message_get_all_recipients(mimemsg); - adrs = internet_address_list_to_string(addresses, NULL, FALSE); - g_object_unref(addresses); - - g_string_append_printf(*gstrp, "%s%s", adrs ? adrs : "", adrs ? "\n" : ""); - g_free(adrs); -} - -static void -accumulate_text_part(MuMsg* msg, MuMsgPart* part, GString** gstrp) -{ - GMimeContentType* ctype; - gboolean err; - char* txt; - - ctype = g_mime_object_get_content_type((GMimeObject*)part->data); - if (!g_mime_content_type_is_type(ctype, "text", "plain")) - return; /* not plain text */ - - txt = mu_msg_mime_part_to_string((GMimePart*)part->data, &err); - if (txt) - g_string_append(*gstrp, txt); - - g_free(txt); -} - -static void -accumulate_text(MuMsg* msg, MuMsgPart* part, GString** gstrp) -{ - if (GMIME_IS_MESSAGE(part->data)) - accumulate_text_message(msg, part, gstrp); - else if (GMIME_IS_PART(part->data)) - accumulate_text_part(msg, part, gstrp); -} - -/* declaration, so we can use it earlier */ -static gboolean handle_mime_object(MuMsg* msg, - GMimeObject* mobj, - GMimeObject* parent, - MuMsgOptions opts, - unsigned* index, - gboolean decrypted, - MuMsgPartForeachFunc func, - gpointer user_data); - -static char* -get_text_from_mime_msg(MuMsg* msg, GMimeMessage* mmsg, MuMsgOptions opts) -{ - GString* gstr; - unsigned index; - - index = 1; - gstr = g_string_sized_new(4096); - handle_mime_object(msg, - mmsg->mime_part, - (GMimeObject*)mmsg, - opts, - &index, - FALSE, - (MuMsgPartForeachFunc)accumulate_text, - &gstr); - - return g_string_free(gstr, FALSE); -} - -char* -Mu::mu_msg_part_get_text(MuMsg* msg, MuMsgPart* self, MuMsgOptions opts) -{ - GMimeObject* mobj; - GMimeMessage* mime_msg; - gboolean err; - - g_return_val_if_fail(msg, NULL); - g_return_val_if_fail(self && GMIME_IS_OBJECT(self->data), NULL); - - mobj = (GMimeObject*)self->data; - - err = FALSE; - if (GMIME_IS_PART(mobj)) { - if (self->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN) - return mu_msg_mime_part_to_string((GMimePart*)mobj, &err); - else - return NULL; /* non-text MimePart */ - } - - mime_msg = NULL; - - if (GMIME_IS_MESSAGE_PART(mobj)) - mime_msg = g_mime_message_part_get_message((GMimeMessagePart*)mobj); - else if (GMIME_IS_MESSAGE(mobj)) - mime_msg = (GMimeMessage*)mobj; - - /* apparently, g_mime_message_part_get_message may still - * return NULL */ - if (mime_msg) - return get_text_from_mime_msg(msg, mime_msg, opts); - return NULL; -} - -/* note: this will return -1 in case of error or if the size is - * unknown */ -static ssize_t -get_part_size(GMimePart* part) -{ - GMimeDataWrapper* wrapper; - GMimeStream* stream; - - wrapper = g_mime_part_get_content(part); - if (!GMIME_IS_DATA_WRAPPER(wrapper)) - return -1; - - stream = g_mime_data_wrapper_get_stream(wrapper); - if (!stream) - return -1; /* no stream -> size is 0 */ - else - return g_mime_stream_length(stream); - - /* NOTE: stream/wrapper are owned by gmime, no unreffing */ -} - -static char* -cleanup_filename(char* fname) -{ - GString* gstr; - gchar* cur; - gunichar uc; - - gstr = g_string_sized_new(strlen(fname)); - - /* replace control characters, slashes, and colons by '-' */ - for (cur = fname; cur && *cur; cur = g_utf8_next_char(cur)) { - uc = g_utf8_get_char(cur); - if (g_unichar_iscntrl(uc) || uc == '/' || uc == ':' || uc == ';') - g_string_append_unichar(gstr, '-'); - else - g_string_append_unichar(gstr, uc); - } - - g_free(fname); - return g_string_free(gstr, FALSE); -} - -/* - * when a part doesn't have a filename, it can be useful to 'guess' one based on - * its mime-type, which allows other tools to handle them correctly, e.g. from - * mu4e. - * - * For now, we only handle calendar invitations in that way, but others may - * follow. - */ -static char* -guess_file_name(GMimeObject* mobj, unsigned index) -{ - GMimeContentType* ctype; - - ctype = g_mime_object_get_content_type(mobj); - - /* special case for calendars; map to '.vcs' */ - if (g_mime_content_type_is_type(ctype, "text", "calendar")) - return g_strdup_printf("vcal-%u.vcs", index); - - /* fallback */ - return g_strdup_printf("%u.msgpart", index); -} - -static char* -mime_part_get_filename(GMimeObject* mobj, unsigned index, gboolean construct_if_needed) -{ - gchar* fname; - - fname = NULL; - - if (GMIME_IS_PART(mobj)) { - /* the easy case: the part has a filename */ - fname = (gchar*)g_mime_part_get_filename(GMIME_PART(mobj)); - if (fname) /* don't include directory components */ - fname = g_path_get_basename(fname); - } - - if (!fname && !construct_if_needed) - return NULL; - - if (GMIME_IS_MESSAGE_PART(mobj)) { - GMimeMessage* msg; - const char* subj; - msg = g_mime_message_part_get_message(GMIME_MESSAGE_PART(mobj)); - subj = g_mime_message_get_subject(msg); - fname = g_strdup_printf("%s.eml", subj ? subj : "message"); - } - - if (!fname) - fname = guess_file_name(mobj, index); - - /* replace control characters, slashes, and colons */ - fname = cleanup_filename(fname); - - return fname; -} - -char* -Mu::mu_msg_part_get_filename(MuMsgPart* mpart, gboolean construct_if_needed) -{ - g_return_val_if_fail(mpart, NULL); - g_return_val_if_fail(GMIME_IS_OBJECT(mpart->data), NULL); - - return mime_part_get_filename((GMimeObject*)mpart->data, mpart->index, construct_if_needed); -} - -const gchar* -Mu::mu_msg_part_get_content_id(MuMsgPart* mpart) -{ - g_return_val_if_fail(mpart, NULL); - g_return_val_if_fail(GMIME_IS_OBJECT(mpart->data), NULL); - return g_mime_object_get_content_id((GMimeObject*)mpart->data); -} - -static MuMsgPartType -get_disposition(GMimeObject* mobj) -{ - const char* disp; - - disp = g_mime_object_get_disposition(mobj); - if (!disp) - return MU_MSG_PART_TYPE_NONE; - - if (strcasecmp(disp, GMIME_DISPOSITION_ATTACHMENT) == 0) - return MU_MSG_PART_TYPE_ATTACHMENT; - - if (strcasecmp(disp, GMIME_DISPOSITION_INLINE) == 0) - return MU_MSG_PART_TYPE_INLINE; - - return MU_MSG_PART_TYPE_NONE; -} - -/* call 'func' with information about this MIME-part */ -static inline void -check_signature(MuMsg* msg, GMimeMultipartSigned* part, MuMsgOptions opts) -{ - GError* err; - - err = NULL; - mu_msg_crypto_verify_part(part, opts, &err); - if (err) { - g_warning("error verifying signature: %s", err->message); - g_clear_error(&err); - } -} - -/* Note: this is function will be called by GMime when it needs a - * password. However, GMime <= 2.6.10 does not handle - * getting passwords correctly, so this might fail. see: - * password_requester in mu-msg-crypto.c */ -static gchar* -get_console_pw(const char* user_id, const char* prompt_ctx, gboolean reprompt, gpointer user_data) -{ - char *prompt, *pass; - - if (!g_mime_check_version(2, 6, 11)) - g_printerr("*** the gmime library you are using has version " - "%u.%u.%u (<= 2.6.10)\n" - "*** this version has a bug in its password " - "retrieval routine, and probably won't work.\n", - gmime_major_version, - gmime_minor_version, - gmime_micro_version); - - if (reprompt) - g_print("Authentication failed. Please try again\n"); - - prompt = g_strdup_printf("Password for %s: ", user_id); - - pass = mu_util_read_password(prompt); - g_free(prompt); - - return pass; -} - -static gboolean -handle_encrypted_part(MuMsg* msg, - GMimeMultipartEncrypted* part, - MuMsgOptions opts, - unsigned* index, - MuMsgPartForeachFunc func, - gpointer user_data) -{ - GError* err; - gboolean rv; - GMimeObject* dec; - MuMsgPartPasswordFunc pw_func; - - if (opts & MU_MSG_OPTION_CONSOLE_PASSWORD) - pw_func = (MuMsgPartPasswordFunc)get_console_pw; - else - pw_func = NULL; - - err = NULL; - dec = mu_msg_crypto_decrypt_part(part, opts, pw_func, NULL, &err); - if (err) { - g_warning("error decrypting part: %s", err->message); - g_clear_error(&err); - } - - if (dec) { - rv = handle_mime_object(msg, - dec, - (GMimeObject*)part, - opts, - index, - TRUE, - func, - user_data); - g_object_unref(dec); - } else { - /* On failure to decrypt, list the encrypted part as - * an attachment - */ - GMimeObject* encrypted; - - encrypted = g_mime_multipart_get_part(GMIME_MULTIPART(part), 1); - - g_return_val_if_fail(GMIME_IS_PART(encrypted), FALSE); - - rv = handle_mime_object(msg, - encrypted, - (GMimeObject*)part, - opts, - index, - FALSE, - func, - user_data); - } - - return rv; -} - -static gboolean -looks_like_text_body_part(GMimeContentType* ctype) -{ - unsigned u; - static struct { - const char* type; - const char* subtype; - } types[] = { - {"text", "plain"}, - {"text", "x-markdown"}, - {"text", "x-diff"}, - {"text", "x-patch"}, - {"application", "x-patch"} - /* possible other types */ - }; - - for (u = 0; u != G_N_ELEMENTS(types); ++u) - if (g_mime_content_type_is_type(ctype, types[u].type, types[u].subtype)) - return TRUE; - - return FALSE; -} - -static MuMsgPartSigStatusReport* -copy_status_report_maybe(GObject* obj) -{ - MuMsgPartSigStatusReport *report, *copy; - - report = (MuMsgPartSigStatusReport*)g_object_get_data(obj, SIG_STATUS_REPORT); - if (!report) - return NULL; /* nothing to copy */ - - copy = g_slice_new0(MuMsgPartSigStatusReport); - copy->verdict = report->verdict; - - if (report->report) - copy->report = g_strdup(report->report); - if (report->signers) - copy->signers = g_strdup(report->signers); - - return copy; -} - -/* call 'func' with information about this MIME-part */ -static gboolean -handle_part(MuMsg* msg, - GMimePart* part, - GMimeObject* parent, - MuMsgOptions opts, - unsigned* index, - gboolean decrypted, - MuMsgPartForeachFunc func, - gpointer user_data) -{ - GMimeContentType* ct; - MuMsgPart msgpart; - - memset(&msgpart, 0, sizeof(MuMsgPart)); - - msgpart.size = get_part_size(part); - msgpart.part_type = MU_MSG_PART_TYPE_LEAF; - msgpart.part_type |= get_disposition((GMimeObject*)part); - if (decrypted) - msgpart.part_type |= MU_MSG_PART_TYPE_DECRYPTED; - else if ((opts & MU_MSG_OPTION_DECRYPT) && GMIME_IS_MULTIPART_ENCRYPTED(parent)) - msgpart.part_type |= MU_MSG_PART_TYPE_ENCRYPTED; - - ct = g_mime_object_get_content_type((GMimeObject*)part); - if (GMIME_IS_CONTENT_TYPE(ct)) { - msgpart.type = g_mime_content_type_get_media_type(ct); - msgpart.subtype = g_mime_content_type_get_media_subtype(ct); - /* store in the part_type as well, for quick checking */ - if (looks_like_text_body_part(ct)) - msgpart.part_type |= MU_MSG_PART_TYPE_TEXT_PLAIN; - else if (g_mime_content_type_is_type(ct, "text", "html")) - msgpart.part_type |= MU_MSG_PART_TYPE_TEXT_HTML; - } - - /* put the verification info in the pgp-signature and every - * descendent of a pgp-encrypted part */ - msgpart.sig_status_report = NULL; - if (g_ascii_strcasecmp(msgpart.subtype, "pgp-signature") == 0 || decrypted) { - msgpart.sig_status_report = copy_status_report_maybe(G_OBJECT(parent)); - if (msgpart.sig_status_report) - msgpart.part_type |= MU_MSG_PART_TYPE_SIGNED; - } - - msgpart.data = (gpointer)part; - msgpart.index = (*index)++; - - func(msg, &msgpart, user_data); - - mu_msg_part_sig_status_report_destroy(msgpart.sig_status_report); - - return TRUE; -} - -/* call 'func' with information about this MIME-part */ -static gboolean -handle_message_part(MuMsg* msg, - GMimeMessagePart* mimemsgpart, - GMimeObject* parent, - MuMsgOptions opts, - unsigned* index, - gboolean decrypted, - MuMsgPartForeachFunc func, - gpointer user_data) -{ - MuMsgPart msgpart; - - memset(&msgpart, 0, sizeof(MuMsgPart)); - - msgpart.type = "message"; - msgpart.subtype = "rfc822"; - msgpart.index = (*index)++; - - /* msgpart.size = 0; /\* maybe calculate this? *\/ */ - - msgpart.part_type = MU_MSG_PART_TYPE_MESSAGE; - msgpart.part_type |= get_disposition((GMimeObject*)mimemsgpart); - - msgpart.data = (gpointer)mimemsgpart; - func(msg, &msgpart, user_data); - - if (opts & MU_MSG_OPTION_RECURSE_RFC822) { - GMimeMessage* mmsg; /* may return NULL for some - * messages */ - mmsg = g_mime_message_part_get_message(mimemsgpart); - if (mmsg) - return handle_mime_object(msg, - mmsg->mime_part, - parent, - opts, - index, - decrypted, - func, - user_data); - } - - return TRUE; -} - -static gboolean -handle_multipart(MuMsg* msg, - GMimeMultipart* mpart, - GMimeObject* parent, - MuMsgOptions opts, - unsigned* index, - gboolean decrypted, - MuMsgPartForeachFunc func, - gpointer user_data) -{ - gboolean res; - GMimeObject* part; - guint i; - - res = TRUE; - for (i = 0; i < mpart->children->len; i++) { - part = (GMimeObject*)mpart->children->pdata[i]; - res &= - handle_mime_object(msg, part, parent, opts, index, decrypted, func, user_data); - } - - return res; -} - -static gboolean -handle_mime_object(MuMsg* msg, - GMimeObject* mobj, - GMimeObject* parent, - MuMsgOptions opts, - unsigned* index, - gboolean decrypted, - MuMsgPartForeachFunc func, - gpointer user_data) -{ - if (GMIME_IS_PART(mobj)) - return handle_part(msg, - GMIME_PART(mobj), - parent, - opts, - index, - decrypted, - func, - user_data); - else if (GMIME_IS_MESSAGE_PART(mobj)) - return handle_message_part(msg, - GMIME_MESSAGE_PART(mobj), - parent, - opts, - index, - decrypted, - func, - user_data); - else if ((opts & MU_MSG_OPTION_VERIFY) && GMIME_IS_MULTIPART_SIGNED(mobj)) { - check_signature(msg, GMIME_MULTIPART_SIGNED(mobj), opts); - return handle_multipart(msg, - GMIME_MULTIPART(mobj), - mobj, - opts, - index, - decrypted, - func, - user_data); - } else if ((opts & MU_MSG_OPTION_DECRYPT) && GMIME_IS_MULTIPART_ENCRYPTED(mobj)) - return handle_encrypted_part(msg, - GMIME_MULTIPART_ENCRYPTED(mobj), - opts, - index, - func, - user_data); - else if (GMIME_IS_MULTIPART(mobj)) - return handle_multipart(msg, - GMIME_MULTIPART(mobj), - parent, - opts, - index, - decrypted, - func, - user_data); - return TRUE; -} - -gboolean -Mu::mu_msg_part_foreach(MuMsg* msg, - MuMsgOptions opts, - MuMsgPartForeachFunc func, - gpointer user_data) -{ - unsigned index; - - index = 1; - g_return_val_if_fail(msg, FALSE); - - if (!mu_msg_load_msg_file(msg, NULL)) - return FALSE; - - return handle_mime_object(msg, - msg->_file->_mime_msg->mime_part, - (GMimeObject*)msg->_file->_mime_msg, - opts, - &index, - FALSE, - func, - user_data); -} - -static gboolean -write_part_to_fd(GMimePart* part, int fd, GError** err) -{ - GMimeStream* stream; - GMimeDataWrapper* wrapper; - gboolean rv; - - stream = g_mime_stream_fs_new(fd); - if (!GMIME_IS_STREAM(stream)) { - g_set_error(err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to create stream"); - return FALSE; - } - g_mime_stream_fs_set_owner(GMIME_STREAM_FS(stream), FALSE); - - wrapper = g_mime_part_get_content(part); - if (!GMIME_IS_DATA_WRAPPER(wrapper)) { - g_set_error(err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to create wrapper"); - g_object_unref(stream); - return FALSE; - } - g_object_ref(part); /* FIXME: otherwise, the unrefs below - * give errors...*/ - - if (g_mime_data_wrapper_write_to_stream(wrapper, stream) == -1) { - rv = FALSE; - g_set_error(err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "failed to write to stream"); - } else - rv = TRUE; - - /* g_object_unref (wrapper); we don't own it */ - g_object_unref(stream); - - return rv; -} - -static gboolean -write_object_to_fd(GMimeObject* obj, int fd, GError** err) -{ - gchar* str; - str = g_mime_object_to_string(obj, NULL); - - if (!str) { - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_GMIME, - "could not get string from object"); - return FALSE; - } - - if (write(fd, str, strlen(str)) == -1) { - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_GMIME, - "failed to write object: %s", - g_strerror(errno)); - return FALSE; - } - - return TRUE; -} - -static gboolean -save_object(GMimeObject* obj, MuMsgOptions opts, const char* fullpath, GError** err) -{ - int fd; - gboolean rv; - gboolean use_existing, overwrite; - - use_existing = opts & MU_MSG_OPTION_USE_EXISTING; - overwrite = opts & MU_MSG_OPTION_OVERWRITE; - - /* don't try to overwrite when we already have it; useful when - * you're sure it's not a different file with the same name */ - if (use_existing && access(fullpath, F_OK) == 0) - return TRUE; - - /* ok, try to create the file */ - fd = mu_util_create_writeable_fd(fullpath, 0600, overwrite); - if (fd == -1) { - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_FILE, - "could not open '%s' for writing: %s", - fullpath, - errno ? g_strerror(errno) : "error"); - return FALSE; - } - - if (GMIME_IS_PART(obj)) - rv = write_part_to_fd((GMimePart*)obj, fd, err); - else - rv = write_object_to_fd(obj, fd, err); - - if (close(fd) != 0 && !err) { /* don't write on top of old err */ - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_FILE, - "could not close '%s': %s", - fullpath, - errno ? g_strerror(errno) : "error"); - return FALSE; - } - - return rv; -} - -gchar* -Mu::mu_msg_part_get_path(MuMsg* msg, - MuMsgOptions opts, - const char* targetdir, - unsigned index, - GError** err) -{ - char * fname, *filepath; - GMimeObject* mobj; - - g_return_val_if_fail(msg, NULL); - - if (!mu_msg_load_msg_file(msg, NULL)) - return NULL; - - mobj = get_mime_object_at_index(msg, opts, index); - if (!mobj) { - mu_util_g_set_error(err, MU_ERROR_GMIME, "cannot find part %u", index); - return NULL; - } - - fname = mime_part_get_filename(mobj, index, TRUE); - filepath = g_build_path(G_DIR_SEPARATOR_S, targetdir ? targetdir : "", fname, NULL); - - /* Unref it since it was referenced earlier by - * get_mime_object_at_index */ - g_object_unref(mobj); - g_free(fname); - - return filepath; -} - -gchar* -Mu::mu_msg_part_get_cache_path(MuMsg* msg, MuMsgOptions opts, guint partid, GError** err) -{ - char * dirname, *filepath; - const char* path; - - g_return_val_if_fail(msg, NULL); - - if (!mu_msg_load_msg_file(msg, NULL)) - return NULL; - - path = mu_msg_get_path(msg); - - /* g_compute_checksum_for_string may be better, but requires - * rel. new glib (2.16) */ - dirname = g_strdup_printf("%s%c%x%c%u", - mu_util_cache_dir(), - G_DIR_SEPARATOR, - g_str_hash(path), - G_DIR_SEPARATOR, - partid); - - if (!mu_util_create_dir_maybe(dirname, 0700, FALSE)) { - mu_util_g_set_error(err, MU_ERROR_FILE, "failed to create dir %s", dirname); - g_free(dirname); - return NULL; - } - - filepath = mu_msg_part_get_path(msg, opts, dirname, partid, err); - g_free(dirname); - - return filepath; -} - -gboolean -Mu::mu_msg_part_save(MuMsg* msg, - MuMsgOptions opts, - const char* fullpath, - guint partidx, - GError** err) -{ - gboolean rv; - GMimeObject* part; - - g_return_val_if_fail(msg, FALSE); - g_return_val_if_fail(fullpath, FALSE); - g_return_val_if_fail( - !((opts & MU_MSG_OPTION_OVERWRITE) && (opts & MU_MSG_OPTION_USE_EXISTING)), - FALSE); - - rv = FALSE; - - if (!mu_msg_load_msg_file(msg, err)) - return rv; - - part = get_mime_object_at_index(msg, opts, partidx); - - /* special case: convert a message-part into a message */ - if (GMIME_IS_MESSAGE_PART(part)) - part = (GMimeObject*)g_mime_message_part_get_message(GMIME_MESSAGE_PART(part)); - - if (!part) - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_GMIME, - "part %u does not exist", - partidx); - else if (!GMIME_IS_PART(part) && !GMIME_IS_MESSAGE(part)) - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_GMIME, - "unexpected type %s for part %u", - G_OBJECT_TYPE_NAME((GObject*)part), - partidx); - else - rv = save_object(part, opts, fullpath, err); - - g_clear_object(&part); - - return rv; -} - -gchar* -Mu::mu_msg_part_save_temp(MuMsg* msg, MuMsgOptions opts, guint partidx, GError** err) -{ - gchar* filepath; - - filepath = mu_msg_part_get_cache_path(msg, opts, partidx, err); - if (!filepath) - return NULL; - - if (!mu_msg_part_save(msg, opts, filepath, partidx, err)) { - g_free(filepath); - return NULL; - } - - return filepath; -} - -static gboolean -match_cid(MuMsgPart* mpart, const char* cid) -{ - const char* this_cid; - - this_cid = g_mime_object_get_content_id((GMimeObject*)mpart->data); - - return g_strcmp0(this_cid, cid) ? TRUE : FALSE; -} - -int -Mu::mu_msg_find_index_for_cid(MuMsg* msg, MuMsgOptions opts, const char* sought_cid) -{ - const char* cid; - - g_return_val_if_fail(msg, -1); - g_return_val_if_fail(sought_cid, -1); - - if (!mu_msg_load_msg_file(msg, NULL)) - return -1; - - cid = g_str_has_prefix(sought_cid, "cid:") ? sought_cid + 4 : sought_cid; - - return get_matching_part_index(msg, opts, (MuMsgPartMatchFunc)match_cid, (gpointer)cid); -} - -struct _RxMatchData { - GSList* _lst; - const GRegex* _rx; - guint _idx; -}; -typedef struct _RxMatchData RxMatchData; - -static void -match_filename_rx(MuMsg* msg, MuMsgPart* mpart, RxMatchData* mdata) -{ - char* fname; - - fname = mu_msg_part_get_filename(mpart, FALSE); - if (!fname) - return; - - if (g_regex_match(mdata->_rx, fname, (GRegexMatchFlags)0, NULL)) - mdata->_lst = g_slist_prepend(mdata->_lst, GUINT_TO_POINTER(mpart->index)); - g_free(fname); -} - -GSList* -Mu::mu_msg_find_files(MuMsg* msg, MuMsgOptions opts, const GRegex* pattern) -{ - RxMatchData mdata; - - g_return_val_if_fail(msg, NULL); - g_return_val_if_fail(pattern, NULL); - - if (!mu_msg_load_msg_file(msg, NULL)) - return NULL; - - mdata._lst = NULL; - mdata._rx = pattern; - mdata._idx = 0; - - mu_msg_part_foreach(msg, opts, (MuMsgPartForeachFunc)match_filename_rx, &mdata); - return mdata._lst; -} - -gboolean -Mu::mu_msg_part_maybe_attachment(MuMsgPart* part) -{ - g_return_val_if_fail(part, FALSE); - - /* attachments must be leaf parts */ - if (!(part->part_type & MU_MSG_PART_TYPE_LEAF)) - return FALSE; - - /* parts other than text/plain, text/html are considered - * attachments as well */ - if (!(part->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN) && - !(part->part_type & MU_MSG_PART_TYPE_TEXT_HTML)) - return TRUE; - - return part->part_type & MU_MSG_PART_TYPE_ATTACHMENT ? TRUE : FALSE; -} diff --git a/lib/mu-msg-part.hh b/lib/mu-msg-part.hh deleted file mode 100644 index 3aa33b53..00000000 --- a/lib/mu-msg-part.hh +++ /dev/null @@ -1,248 +0,0 @@ -/* -** Copyright (C) 2008-2020 Dirk-Jan C. Binnema -** -** 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 -#include /* 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__*/ diff --git a/lib/mu-msg-priv.hh b/lib/mu-msg-priv.hh deleted file mode 100644 index 43aa83dd..00000000 --- a/lib/mu-msg-priv.hh +++ /dev/null @@ -1,132 +0,0 @@ -/* -** Copyright (C) 2008-2020 Dirk-Jan C. Binnema -** -** 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 -#include - -#include -#include -#include -#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__*/ diff --git a/lib/mu-msg-sexp.cc b/lib/mu-msg-sexp.cc deleted file mode 100644 index e56cffb0..00000000 --- a/lib/mu-msg-sexp.cc +++ /dev/null @@ -1,390 +0,0 @@ -/* -** Copyright (C) 2011-2022 Dirk-Jan C. Binnema -** -** 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 -#include - -#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("?", - 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; - constexpr std::array 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)); -} diff --git a/lib/mu-msg.cc b/lib/mu-msg.cc deleted file mode 100644 index 1ce4d896..00000000 --- a/lib/mu-msg.cc +++ /dev/null @@ -1,782 +0,0 @@ -/* -** Copyright (C) 2008-2020 Dirk-Jan C. Binnema -** -** 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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -#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(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(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) -{ - typedef const char*(*AddressFunc)(MuMsg*); - using AddressInfo = std::pair; - - 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 , 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)); -} diff --git a/lib/mu-msg.hh b/lib/mu-msg.hh deleted file mode 100644 index f0962106..00000000 --- a/lib/mu-msg.hh +++ /dev/null @@ -1,499 +0,0 @@ -/* -** Copyright (C) 2010-2022 Dirk-Jan C. Binnema -** -** 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 - -#include - -#include -#include -#include -#include - -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={}); -/** - * create a 'display contact' from an email header To/Cc/Bcc/From-type address - * ie., turn - * "Foo Bar" - * 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__*/