/* ** Copyright (C) 2011-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 #include #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 Sexp make_contact_sexp (MuMsgContact *c) { // a cons-pair...perhaps make this a plist too? Sexp::List contact; if (mu_msg_contact_name(c)) contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_name(c)))); else contact.add(Sexp::make_symbol("nil")); contact.add(Sexp::make_symbol(".")); contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_email(c)))); return Sexp::make_list(std::move(contact)); } 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); MuMsgContact contact{NULL, address}; list.add_prop(":list-post", Sexp::make_list(make_contact_sexp(&contact))); g_free(address); } g_match_info_free (minfo); g_regex_unref (rx); } struct _ContactData { Sexp::List from, to, cc, bcc, reply_to; }; typedef struct _ContactData ContactData; static gboolean each_contact (MuMsgContact *c, ContactData *cdata) { switch (mu_msg_contact_type (c)) { case MU_MSG_CONTACT_TYPE_FROM: cdata->from.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_TO: cdata->to.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_CC: cdata->cc.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_BCC: cdata->bcc.add(make_contact_sexp(c)); break; case MU_MSG_CONTACT_TYPE_REPLY_TO: cdata->reply_to.add(make_contact_sexp(c)); break; default: g_return_val_if_reached (FALSE); return FALSE; } return TRUE; } static void add_prop_nonempty_list(Sexp::List& list, std::string&& name, Sexp::List&& sexp) { if (sexp.empty()) return; list.add_prop(std::move(name), Sexp::make_list(std::move(sexp))); } static void add_contacts (Sexp::List& list, MuMsg *msg) { ContactData cdata{}; mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact, &cdata); add_prop_nonempty_list (list, ":from", std::move(cdata.from)); add_prop_nonempty_list (list, ":to", std::move(cdata.to)); add_prop_nonempty_list (list, ":cc", std::move(cdata.cc)); add_prop_nonempty_list (list, ":bcc", std::move(cdata.bcc)); add_prop_nonempty_list (list, ":reply-to", std::move(cdata.reply_to)); add_list_post (list, msg); } typedef struct { Sexp::List flaglist; MuFlags msgflags; } FlagData; static void each_flag (MuFlags flag, FlagData *fdata) { if (flag & fdata->msgflags) fdata->flaglist.add(Sexp::make_symbol(mu_flag_name(flag))); } static void add_flags (Sexp::List& list, MuMsg *msg) { FlagData fdata{}; fdata.msgflags = mu_msg_get_flags (msg); mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata); if (!fdata.flaglist.empty()) list.add_prop(":flags", Sexp::make_list(std::move(fdata.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(mu_msg_prio_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_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)); }