/* ** Copyright (C) 2011-2017 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-str.h" #include "mu-msg.h" #include "mu-msg-iter.h" #include "mu-msg-part.h" #include "mu-maildir.h" static void append_sexp_attr_list (GString *gstr, const char* elm, const GSList *lst) { const GSList *cur; if (!lst) return; /* empty list, don't include */ g_string_append_printf (gstr, "\t:%s ( ", elm); for (cur = lst; cur; cur = g_slist_next(cur)) { char *str; str = mu_str_escape_c_literal ((const gchar*)cur->data, TRUE); g_string_append_printf (gstr, "%s ", str); g_free (str); } g_string_append (gstr, ")\n"); } static void append_sexp_attr (GString *gstr, const char* elm, const char *str) { gchar *esc, *utf8, *cur; if (!str || strlen(str) == 0) return; /* empty: don't include */ utf8 = mu_str_utf8ify (str); for (cur = utf8; *cur; ++cur) if (iscntrl(*cur)) *cur = ' '; esc = mu_str_escape_c_literal (utf8, TRUE); g_free (utf8); g_string_append_printf (gstr, "\t:%s %s\n", elm, esc); g_free (esc); } static void append_sexp_body_attr (GString *gstr, const char* elm, const char *str) { gchar *esc; if (!str || strlen(str) == 0) return; /* empty: don't include */ esc = mu_str_escape_c_literal (str, TRUE); g_string_append_printf (gstr, "\t:%s %s\n", elm, esc); g_free (esc); } struct _ContactData { gboolean from, to, cc, bcc, reply_to; GString *gstr; MuMsgContactType prev_ctype; }; typedef struct _ContactData ContactData; static gchar* get_name_email_pair (MuMsgContact *c) { gchar *name, *email, *pair; name = (char*)mu_msg_contact_name(c); email = (char*)mu_msg_contact_email(c); name = name ? mu_str_escape_c_literal (name, TRUE) : NULL; email = email ? mu_str_escape_c_literal (email, TRUE) : NULL; pair = g_strdup_printf ("(%s . %s)", name ? name : "nil", email ? email : "nil"); g_free (name); g_free (email); return pair; } static void add_prefix_maybe (GString *gstr, gboolean *field, const char *prefix) { /* if there's nothing in the field yet, add the prefix */ if (!*field) g_string_append (gstr, prefix); *field = TRUE; } static gboolean each_contact (MuMsgContact *c, ContactData *cdata) { char *pair; MuMsgContactType ctype; ctype = mu_msg_contact_type (c); /* if the current type is not the previous type, close the * previous first */ if (cdata->prev_ctype != ctype && cdata->prev_ctype != (unsigned)-1) g_string_append (cdata->gstr, ")\n"); switch (ctype) { case MU_MSG_CONTACT_TYPE_FROM: add_prefix_maybe (cdata->gstr, &cdata->from, "\t:from ("); break; case MU_MSG_CONTACT_TYPE_TO: add_prefix_maybe (cdata->gstr, &cdata->to, "\t:to ("); break; case MU_MSG_CONTACT_TYPE_CC: add_prefix_maybe (cdata->gstr, &cdata->cc, "\t:cc ("); break; case MU_MSG_CONTACT_TYPE_BCC: add_prefix_maybe (cdata->gstr, &cdata->bcc, "\t:bcc ("); break; case MU_MSG_CONTACT_TYPE_REPLY_TO: add_prefix_maybe (cdata->gstr, &cdata->reply_to, "\t:reply-to ("); break; default: g_return_val_if_reached (FALSE); } cdata->prev_ctype = ctype; pair = get_name_email_pair (c); g_string_append (cdata->gstr, pair); g_free (pair); return TRUE; } static void maybe_append_list_post_as_reply_to (GString *gstr, 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, 0, NULL); g_return_if_fail(rx); if (g_regex_match (rx, list_post, 0, &minfo)) { char *addr; addr = g_match_info_fetch (minfo, 2); g_string_append_printf (gstr,"\t:reply-to ((nil . \"%s\"))\n", addr); g_free(addr); } g_match_info_free (minfo); g_regex_unref (rx); } static void append_sexp_contacts (GString *gstr, MuMsg *msg) { ContactData cdata; cdata.from = cdata.to = cdata.cc = cdata.bcc = cdata.reply_to = FALSE; cdata.gstr = gstr; cdata.prev_ctype = (unsigned)-1; mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact, &cdata); if (cdata.from || cdata.to || cdata.cc || cdata.bcc || cdata.reply_to) gstr = g_string_append (gstr, ")\n"); if (!cdata.reply_to) maybe_append_list_post_as_reply_to (gstr, msg); } struct _FlagData { char *flagstr; MuFlags msgflags; }; typedef struct _FlagData FlagData; static void each_flag (MuFlags flag, FlagData *fdata) { if (!(flag & fdata->msgflags)) return; if (!fdata->flagstr) fdata->flagstr = g_strdup (mu_flag_name(flag)); else { gchar *tmp; tmp = g_strconcat (fdata->flagstr, " ", mu_flag_name(flag), NULL); g_free (fdata->flagstr); fdata->flagstr = tmp; } } static void append_sexp_flags (GString *gstr, MuMsg *msg) { FlagData fdata; fdata.msgflags = mu_msg_get_flags (msg); fdata.flagstr = NULL; mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata); if (fdata.flagstr) g_string_append_printf (gstr, "\t:flags (%s)\n", fdata.flagstr); g_free (fdata.flagstr); } 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) { char *tmp, *tmpfile; opts |= MU_MSG_OPTION_USE_EXISTING; if (!(opts & MU_MSG_OPTION_EXTRACT_IMAGES) || g_ascii_strcasecmp (part->type, "image") != 0) return NULL; tmp = get_temp_file (msg, opts, part->index); if (!tmp) return NULL; tmpfile = mu_str_escape_c_literal (tmp, TRUE); g_free (tmp); return tmpfile; } struct _PartInfo { char *parts; MuMsgOptions opts; }; typedef struct _PartInfo PartInfo; static char* sig_verdict (MuMsgPart *mpart) { char *signers, *s; const char *verdict; MuMsgPartSigStatusReport *report; report = mpart->sig_status_report; if (!report) return g_strdup (""); switch (report->verdict) { case MU_MSG_PART_SIG_STATUS_GOOD: verdict = ":signature verified"; break; case MU_MSG_PART_SIG_STATUS_BAD: verdict = ":signature bad"; break; case MU_MSG_PART_SIG_STATUS_ERROR: verdict = ":signature unverified"; break; default: verdict = ""; break; } if (!report->signers) return g_strdup (verdict); signers = mu_str_escape_c_literal (report->signers, TRUE); s = g_strdup_printf ("%s :signers %s", verdict, signers); g_free (signers); return s; } static const char* dec_verdict (MuMsgPart *mpart) { MuMsgPartType ptype; ptype = mpart->part_type; if (ptype & MU_MSG_PART_TYPE_DECRYPTED) return ":decryption succeeded"; else if (ptype & MU_MSG_PART_TYPE_ENCRYPTED) return ":decryption failed"; else return ""; } static gchar * get_part_type_string (MuMsgPartType ptype) { GString *gstr; unsigned u; 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" } }; gstr = g_string_sized_new (100); /* more than enough */ gstr = g_string_append_c (gstr, '('); for (u = 0; u!= G_N_ELEMENTS(ptypes); ++u) { if (ptype & ptypes[u].ptype) { if (gstr->len > 1) gstr = g_string_append_c (gstr, ' '); gstr = g_string_append (gstr, ptypes[u].name); } } gstr = g_string_append_c (gstr, ')'); return g_string_free (gstr, FALSE); } static void each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo) { char *name, *encname, *tmp, *parttype; char *tmpfile, *cidesc, *verdict; const char *cid; name = mu_msg_part_get_filename (part, TRUE); encname = name ? mu_str_escape_c_literal(name, TRUE) : g_strdup("\"noname\""); g_free (name); tmpfile = get_temp_file_maybe (msg, part, pinfo->opts); parttype = get_part_type_string (part->part_type); verdict = sig_verdict (part); cid = mu_msg_part_get_content_id(part); cidesc = cid ? mu_str_escape_c_literal(cid, TRUE) : NULL; tmp = g_strdup_printf ("%s(:index %d :name %s :mime-type \"%s/%s\"%s%s " ":type %s " ":attachment %s %s%s :size %i %s %s)", pinfo->parts ? pinfo->parts: "", part->index, encname, part->type ? part->type : "application", part->subtype ? part->subtype : "octet-stream", tmpfile ? " :temp" : "", tmpfile ? tmpfile : "", parttype, mu_msg_part_maybe_attachment (part) ? "t" : "nil", cidesc ? " :cid" : "", cidesc ? cidesc : "", (int)part->size, verdict, dec_verdict (part)); g_free (encname); g_free (tmpfile); g_free (parttype); g_free (verdict); g_free (cidesc); g_free (pinfo->parts); pinfo->parts = tmp; } static void append_sexp_parts (GString *gstr, MuMsg *msg, MuMsgOptions opts) { PartInfo pinfo; pinfo.parts = NULL; pinfo.opts = opts; if (!mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)each_part, &pinfo)) { /* do nothing */ } else if (pinfo.parts) { g_string_append_printf (gstr, "\t:parts (%s)\n", pinfo.parts); g_free (pinfo.parts); } } static void append_sexp_thread_info (GString *gstr, const MuMsgIterThreadInfo *ti) { g_string_append_printf (gstr, "\t:thread (:path \"%s\" :level %u%s%s%s%s%s)\n", ti->threadpath, ti->level, ti->prop & MU_MSG_ITER_THREAD_PROP_FIRST_CHILD ? " :first-child t" : "", ti->prop & MU_MSG_ITER_THREAD_PROP_LAST_CHILD ? " :last-child t" : "", ti->prop & MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT ? " :empty-parent t" : "", ti->prop & MU_MSG_ITER_THREAD_PROP_DUP ? " :duplicate t" : "", ti->prop & MU_MSG_ITER_THREAD_PROP_HAS_CHILD ? " :has-child t" : ""); } static void append_sexp_param (GString *gstr, const GSList *param) { for (;param; param = g_slist_next (param)) { const char *str; char *key, *value; str = param->data; key = str ? mu_str_escape_c_literal (str, FALSE) : g_strdup (""); param = g_slist_next (param); str = param->data; value = str ? mu_str_escape_c_literal (str, FALSE) : g_strdup (""); g_string_append_printf (gstr, "(\"%s\" . \"%s\")", key, value); g_free (key); g_free (value); if (param->next) g_string_append_c (gstr, ' '); } } static void append_message_file_parts (GString *gstr, MuMsg *msg, MuMsgOptions opts) { const char *str; GError *err; const GSList *params; 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; } append_sexp_parts (gstr, msg, opts); append_sexp_contacts (gstr, msg); /* add the user-agent / x-mailer */ str = mu_msg_get_header (msg, "User-Agent"); if (str || (str = mu_msg_get_header (msg, "X-Mailer"))) append_sexp_attr (gstr, "user-agent", str); params = mu_msg_get_body_text_content_type_parameters (msg, opts); if (params) { g_string_append_printf (gstr, "\t:body-txt-params ("); append_sexp_param (gstr, params); g_string_append_printf (gstr, ")\n"); } append_sexp_body_attr (gstr, "body-txt", mu_msg_get_body_text(msg, opts)); append_sexp_body_attr (gstr, "body-html", mu_msg_get_body_html(msg, opts)); } static void append_sexp_date_and_size (GString *gstr, MuMsg *msg) { time_t t; size_t s; t = mu_msg_get_date (msg); if (t == (time_t)-1) /* invalid date? */ t = 0; s = mu_msg_get_size (msg); if (s == (size_t)-1) /* invalid size? */ s = 0; g_string_append_printf (gstr, "\t:date (%d %u 0)\n\t:size %u\n", (unsigned)(t >> 16), (unsigned)(t & 0xffff), (unsigned)s); } static void append_sexp_tags (GString *gstr, MuMsg *msg) { const GSList *tags, *t; gchar *tagesc; GString *tagstr = g_string_new(""); tags = mu_msg_get_tags (msg); for(t = tags; t; t = t->next) { if (t != tags) g_string_append(tagstr, " "); tagesc = mu_str_escape_c_literal((const gchar *)t->data, TRUE); g_string_append(tagstr, tagesc); g_free(tagesc); } if (tagstr->len > 0) g_string_append_printf (gstr, "\t:tags (%s)\n", tagstr->str); g_string_free (tagstr, TRUE); } char* mu_msg_to_sexp (MuMsg *msg, unsigned docid, const MuMsgIterThreadInfo *ti, MuMsgOptions opts) { GString *gstr; g_return_val_if_fail (msg, NULL); g_return_val_if_fail (!((opts & MU_MSG_OPTION_HEADERS_ONLY) && (opts & MU_MSG_OPTION_EXTRACT_IMAGES)),NULL); gstr = g_string_sized_new ((opts & MU_MSG_OPTION_HEADERS_ONLY) ? 1024 : 8192); if (docid == 0) g_string_append (gstr, "(\n"); else g_string_append_printf (gstr, "(\n\t:docid %u\n", docid); if (ti) append_sexp_thread_info (gstr, ti); append_sexp_attr (gstr, "subject", mu_msg_get_subject (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) append_sexp_contacts (gstr, msg); append_sexp_date_and_size (gstr, msg); append_sexp_attr (gstr, "message-id", mu_msg_get_msgid (msg)); append_sexp_attr (gstr, "mailing-list", mu_msg_get_mailing_list (msg)); append_sexp_attr (gstr, "path", mu_msg_get_path (msg)); append_sexp_attr (gstr, "maildir", mu_msg_get_maildir (msg)); g_string_append_printf (gstr, "\t:priority %s\n", mu_msg_prio_name(mu_msg_get_prio(msg))); append_sexp_flags (gstr, msg); append_sexp_tags (gstr, msg); append_sexp_attr_list (gstr, "references", mu_msg_get_references (msg)); append_sexp_attr (gstr, "in-reply-to", mu_msg_get_header (msg, "In-Reply-To")); /* 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)) append_message_file_parts (gstr, msg, opts); g_string_append (gstr, ")\n"); return g_string_free (gstr, FALSE); }