1 /*
2 ** Copyright (C) 2011-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
3 **
4 ** This program is free software; you can redistribute it and/or modify it
5 ** under the terms of the GNU General Public License as published by the
6 ** Free Software Foundation; either version 3, or (at your option) any
7 ** later version.
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 ** GNU General Public License for more details.
13 **
14 ** You should have received a copy of the GNU General Public License
15 ** along with this program; if not, write to the Free Software Foundation,
16 ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 **
18 */
19 #include <string.h>
20 #include <ctype.h>
21 
22 #include "mu-query-results.hh"
23 #include "utils/mu-str.h"
24 #include "mu-msg.hh"
25 #include "mu-msg-part.hh"
26 #include "mu-maildir.hh"
27 
28 using namespace Mu;
29 
30 static void
add_prop_nonempty(Sexp::List & list,const char * elm,const GSList * str_lst)31 add_prop_nonempty (Sexp::List& list, const char* elm, const GSList *str_lst)
32 {
33         Sexp::List elms;
34         while (str_lst) {
35                 elms.add(Sexp::make_string((const char*)str_lst->data));
36                 str_lst = g_slist_next(str_lst);
37         }
38 
39         if (!elms.empty())
40                 list.add_prop(elm, Sexp::make_list(std::move(elms)));
41 }
42 
43 static void
add_prop_nonempty(Sexp::List & list,const char * name,const char * str)44 add_prop_nonempty (Sexp::List& list, const char* name, const char *str)
45 {
46         if (str && str[0])
47                 list.add_prop(name, Sexp::make_string(str));
48 }
49 
50 
51 static Sexp
make_contact_sexp(MuMsgContact * c)52 make_contact_sexp (MuMsgContact *c)
53 {
54         // a cons-pair...perhaps make this a plist too?
55 
56         Sexp::List contact;
57         if (mu_msg_contact_name(c))
58                 contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_name(c))));
59         else
60                 contact.add(Sexp::make_symbol("nil"));
61 
62         contact.add(Sexp::make_symbol("."));
63         contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_email(c))));
64 
65         return Sexp::make_list(std::move(contact));
66 }
67 
68 static void
add_list_post(Sexp::List & list,MuMsg * msg)69 add_list_post (Sexp::List& list, MuMsg *msg)
70 {
71         /* some mailing lists do not set the reply-to; see pull #1278. So for
72          * those cases, check the List-Post address and use that instead */
73 
74         GMatchInfo      *minfo;
75         GRegex          *rx;
76         const char*      list_post;
77 
78         list_post = mu_msg_get_header (msg, "List-Post");
79         if (!list_post)
80                 return;
81 
82         rx = g_regex_new ("<?mailto:([a-z0-9!@#$%&'*+-/=?^_`{|}~]+)>?",
83                           G_REGEX_CASELESS, (GRegexMatchFlags)0, NULL);
84         g_return_if_fail(rx);
85 
86         if (g_regex_match (rx, list_post, (GRegexMatchFlags)0, &minfo)) {
87                 auto address = (char*)g_match_info_fetch (minfo, 1);
88                 MuMsgContact contact{NULL,  address};
89                 list.add_prop(":list-post",
90                               Sexp::make_list(make_contact_sexp(&contact)));
91                 g_free(address);
92         }
93 
94         g_match_info_free (minfo);
95         g_regex_unref (rx);
96 }
97 
98 
99 struct _ContactData {
100         Sexp::List from, to, cc, bcc, reply_to;
101 };
102 typedef struct _ContactData ContactData;
103 
104 
105 static gboolean
each_contact(MuMsgContact * c,ContactData * cdata)106 each_contact (MuMsgContact *c, ContactData *cdata)
107 {
108         switch (mu_msg_contact_type (c)) {
109 
110         case MU_MSG_CONTACT_TYPE_FROM:
111                 cdata->from.add(make_contact_sexp(c));
112                 break;
113         case MU_MSG_CONTACT_TYPE_TO:
114                 cdata->to.add(make_contact_sexp(c));
115                 break;
116         case MU_MSG_CONTACT_TYPE_CC:
117                 cdata->cc.add(make_contact_sexp(c));
118                 break;
119         case MU_MSG_CONTACT_TYPE_BCC:
120                 cdata->bcc.add(make_contact_sexp(c));
121                 break;
122         case MU_MSG_CONTACT_TYPE_REPLY_TO:
123                 cdata->reply_to.add(make_contact_sexp(c));
124                 break;
125         default:
126                 g_return_val_if_reached (FALSE);
127                 return FALSE;
128         }
129         return TRUE;
130 }
131 
132 static void
add_prop_nonempty_list(Sexp::List & list,std::string && name,Sexp::List && sexp)133 add_prop_nonempty_list(Sexp::List& list, std::string&& name,
134                        Sexp::List&& sexp)
135 {
136         if (sexp.empty())
137                 return;
138 
139         list.add_prop(std::move(name),
140                          Sexp::make_list(std::move(sexp)));
141 }
142 
143 
144 static void
add_contacts(Sexp::List & list,MuMsg * msg)145 add_contacts (Sexp::List& list, MuMsg *msg)
146 {
147         ContactData cdata{};
148         mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact,
149                                 &cdata);
150 
151         add_prop_nonempty_list (list, ":from",     std::move(cdata.from));
152         add_prop_nonempty_list (list, ":to",       std::move(cdata.to));
153         add_prop_nonempty_list (list, ":cc",       std::move(cdata.cc));
154         add_prop_nonempty_list (list, ":bcc",      std::move(cdata.bcc));
155         add_prop_nonempty_list (list, ":reply-to", std::move(cdata.reply_to));
156 
157         add_list_post (list, msg);
158 }
159 
160 typedef struct {
161         Sexp::List flaglist;
162         MuFlags    msgflags;
163 } FlagData;
164 
165 static void
each_flag(MuFlags flag,FlagData * fdata)166 each_flag (MuFlags flag, FlagData *fdata)
167 {
168         if (flag & fdata->msgflags)
169                 fdata->flaglist.add(Sexp::make_symbol(mu_flag_name(flag)));
170 }
171 
172 static void
add_flags(Sexp::List & list,MuMsg * msg)173 add_flags (Sexp::List& list, MuMsg *msg)
174 {
175         FlagData fdata{};
176         fdata.msgflags = mu_msg_get_flags (msg);
177 
178         mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata);
179         if (!fdata.flaglist.empty())
180                 list.add_prop(":flags",
181                               Sexp::make_list(std::move(fdata.flaglist)));
182 }
183 
184 static char*
get_temp_file(MuMsg * msg,MuMsgOptions opts,unsigned index)185 get_temp_file (MuMsg *msg, MuMsgOptions opts, unsigned index)
186 {
187         char *path;
188         GError *err;
189 
190         err = NULL;
191         path = mu_msg_part_get_cache_path (msg, opts, index, &err);
192         if (!path)
193                 goto errexit;
194 
195         if (!mu_msg_part_save (msg, opts, path, index, &err))
196                 goto errexit;
197 
198         return path;
199 
200 errexit:
201         g_warning ("failed to save mime part: %s",
202                    err->message ? err->message : "something went wrong");
203         g_clear_error (&err);
204         g_free (path);
205 
206         return NULL;
207 }
208 
209 static gchar*
get_temp_file_maybe(MuMsg * msg,MuMsgPart * part,MuMsgOptions opts)210 get_temp_file_maybe (MuMsg *msg, MuMsgPart *part, MuMsgOptions opts)
211 {
212         opts = (MuMsgOptions)((int)opts | (int)MU_MSG_OPTION_USE_EXISTING);
213         if  (!(opts & MU_MSG_OPTION_EXTRACT_IMAGES) ||
214              g_ascii_strcasecmp (part->type, "image") != 0)
215                 return NULL;
216         else
217                 return get_temp_file (msg, opts, part->index);
218 }
219 
220 struct PartInfo {
221         Sexp::List  parts;
222         MuMsgOptions opts;
223 };
224 
225 static void
sig_verdict(Sexp::List & partlist,MuMsgPart * mpart)226 sig_verdict (Sexp::List& partlist, MuMsgPart *mpart)
227 {
228         MuMsgPartSigStatusReport *report = mpart->sig_status_report;
229         if (!report)
230                 return;
231 
232 #pragma GCC diagnostic push
233 #pragma GCC diagnostic ignored   "-Wswitch-enum"
234         switch (report->verdict) {
235         case MU_MSG_PART_SIG_STATUS_GOOD:
236                 partlist.add_prop(":signature", Sexp::make_symbol("verified"));
237                 break;
238         case MU_MSG_PART_SIG_STATUS_BAD:
239                 partlist.add_prop(":signature", Sexp::make_symbol("bad"));
240                 break;
241         case MU_MSG_PART_SIG_STATUS_ERROR:
242                 partlist.add_prop(":signature", Sexp::make_symbol("unverified"));
243                 break;
244         default:
245                 break;
246         }
247 #pragma GCC diagnostic pop
248 
249         if (report->signers)
250                 partlist.add_prop(":signers", Sexp::make_string(report->signers));
251 }
252 
253 static void
dec_verdict(Sexp::List & partlist,MuMsgPart * mpart)254 dec_verdict (Sexp::List& partlist, MuMsgPart *mpart)
255 {
256         if (mpart->part_type & MU_MSG_PART_TYPE_DECRYPTED)
257                 partlist.add_prop(":decryption", Sexp::make_symbol("succeeded"));
258         else if (mpart->part_type & MU_MSG_PART_TYPE_ENCRYPTED)
259                 partlist.add_prop(":decryption", Sexp::make_symbol("failed"));
260 }
261 
262 
263 static Sexp
make_part_types(MuMsgPartType ptype)264 make_part_types (MuMsgPartType ptype)
265 {
266         struct PartTypes {
267                 MuMsgPartType ptype;
268                 const char*   name;
269         } ptypes[] = {
270                 { MU_MSG_PART_TYPE_LEAF,       "leaf" },
271                 { MU_MSG_PART_TYPE_MESSAGE,    "message" },
272                 { MU_MSG_PART_TYPE_INLINE,     "inline" },
273                 { MU_MSG_PART_TYPE_ATTACHMENT, "attachment" },
274                 { MU_MSG_PART_TYPE_SIGNED,     "signed" },
275                 { MU_MSG_PART_TYPE_ENCRYPTED,  "encrypted" }
276         };
277 
278         Sexp::List list;
279         for (auto u = 0U; u!= G_N_ELEMENTS(ptypes); ++u)
280                 if (ptype & ptypes[u].ptype)
281                         list.add(Sexp::make_symbol(ptypes[u].name));
282 
283         return Sexp::make_list(std::move(list));
284 }
285 
286 static void
each_part(MuMsg * msg,MuMsgPart * part,PartInfo * pinfo)287 each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo)
288 {
289         auto mimetype = format("%s/%s",
290                                part->type ? part->type : "application",
291                                part->subtype ? part->subtype : "octet-stream");
292         auto maybe_attach = Sexp::make_symbol(mu_msg_part_maybe_attachment (part) ?
293                                               "t" : "nil");
294         Sexp::List  partlist;
295 
296         partlist.add_prop(":index",     Sexp::make_number(part->index));
297         partlist.add_prop(":mime-type", Sexp::make_string(mimetype));
298         partlist.add_prop(":size",      Sexp::make_number(part->size));
299 
300         dec_verdict (partlist, part);
301         sig_verdict (partlist, part);
302 
303         if (part->part_type)
304                 partlist.add_prop(":type", make_part_types(part->part_type));
305 
306         char *fname = mu_msg_part_get_filename (part, TRUE);
307         if (fname)
308                 partlist.add_prop(":name", Sexp::make_string(fname))                                ;
309         g_free (fname);
310 
311         if (mu_msg_part_maybe_attachment (part))
312                 partlist.add_prop(":attachment", Sexp::make_symbol("t"));
313         const auto cid{ mu_msg_part_get_content_id(part)};
314         if (cid)
315                 partlist.add_prop(":cid", Sexp::make_string(cid));
316 
317         char *tempfile  = get_temp_file_maybe (msg, part, pinfo->opts);
318         if (tempfile)
319                 partlist.add_prop (":temp", Sexp::make_string(tempfile));
320         g_free (tempfile);
321 
322         pinfo->parts.add(Sexp::make_list(std::move(partlist)));
323 }
324 
325 
326 static void
add_parts(Sexp::List & items,MuMsg * msg,MuMsgOptions opts)327 add_parts (Sexp::List& items, MuMsg *msg, MuMsgOptions opts)
328 {
329         PartInfo pinfo;
330         pinfo.opts  = opts;
331 
332         if (mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)each_part, &pinfo) &&
333             !pinfo.parts.empty())
334                 items.add_prop(":parts", Sexp::make_list(std::move(pinfo.parts)));
335 }
336 
337 
338 static void
add_message_file_parts(Sexp::List & items,MuMsg * msg,MuMsgOptions opts)339 add_message_file_parts (Sexp::List& items, MuMsg *msg, MuMsgOptions opts)
340 {
341         GError *err{NULL};
342         if (!mu_msg_load_msg_file (msg, &err)) {
343                 g_warning ("failed to load message file: %s",
344                            err ? err->message : "some error occurred");
345                 g_clear_error (&err);
346                 return;
347         }
348 
349         add_parts    (items, msg, opts);
350         add_contacts (items, msg);
351 
352         /* add the user-agent / x-mailer */
353         auto str = mu_msg_get_header (msg, "User-Agent");
354         if (!str)
355                 str = mu_msg_get_header (msg, "X-Mailer");
356 
357         add_prop_nonempty (items, ":user-agent", str);
358 
359         add_prop_nonempty (items, ":body-txt-params",
360                            mu_msg_get_body_text_content_type_parameters (msg, opts));
361         add_prop_nonempty (items, ":body-txt",  mu_msg_get_body_text(msg, opts));
362         add_prop_nonempty (items, ":body-html", mu_msg_get_body_html(msg, opts));
363 }
364 
365 static void
add_date_and_size(Sexp::List & items,MuMsg * msg)366 add_date_and_size (Sexp::List& items, MuMsg *msg)
367 {
368         auto t = mu_msg_get_date (msg);
369         if (t == (time_t)-1)  /* invalid date? */
370                 t = 0;
371 
372         Sexp::List dlist;
373         dlist.add(Sexp::make_number((unsigned)(t >> 16)));
374         dlist.add(Sexp::make_number((unsigned)(t & 0xffff)));
375         dlist.add(Sexp::make_number(0));
376 
377         items.add_prop(":date", Sexp::make_list(std::move(dlist)));
378 
379         auto s = mu_msg_get_size (msg);
380         if (s == (size_t)-1)   /* invalid size? */
381                 s = 0;
382 
383         items.add_prop(":size", Sexp::make_number(s));
384 }
385 
386 
387 static void
add_tags(Sexp::List & items,MuMsg * msg)388 add_tags (Sexp::List& items, MuMsg *msg)
389 {
390         Sexp::List taglist;
391         for (auto tags = mu_msg_get_tags(msg); tags; tags = g_slist_next(tags))
392                 taglist.add(Sexp::make_string((const char*)tags->data));
393 
394         if (!taglist.empty())
395                 items.add_prop(":tags", Sexp::make_list(std::move(taglist)));
396 }
397 
398 
399 Mu::Sexp::List
msg_to_sexp_list(MuMsg * msg,unsigned docid,MuMsgOptions opts)400 Mu::msg_to_sexp_list (MuMsg *msg, unsigned docid, MuMsgOptions opts)
401 {
402         g_return_val_if_fail (msg, Sexp::List());
403         g_return_val_if_fail (!((opts & MU_MSG_OPTION_HEADERS_ONLY) &&
404                                 (opts & MU_MSG_OPTION_EXTRACT_IMAGES)),
405                               Sexp::List());
406         Sexp::List items;
407 
408         if (docid != 0)
409                 items.add_prop(":docid", Sexp::make_number(docid));
410 
411         add_prop_nonempty (items, ":subject",      mu_msg_get_subject (msg));
412         add_prop_nonempty (items, ":message-id",   mu_msg_get_msgid (msg));
413         add_prop_nonempty (items, ":mailing-list", mu_msg_get_mailing_list (msg));
414         add_prop_nonempty (items, ":path",         mu_msg_get_path (msg));
415         add_prop_nonempty (items, ":maildir",      mu_msg_get_maildir (msg));
416 
417         items.add_prop(":priority", Sexp::make_symbol(mu_msg_prio_name(mu_msg_get_prio(msg))));
418 
419         /* in the no-headers-only case (see below) we get a more complete list of contacts, so no
420          * need to get them here if that's the case */
421         if (opts & MU_MSG_OPTION_HEADERS_ONLY)
422                 add_contacts (items, msg);
423 
424         add_prop_nonempty (items, ":references", mu_msg_get_references (msg));
425         add_prop_nonempty (items, ":in-reply-to", mu_msg_get_header (msg, "In-Reply-To"));
426 
427         add_date_and_size (items, msg);
428         add_flags (items, msg);
429         add_tags (items, msg);
430 
431         /* headers are retrieved from the database, views from the
432          * message file file attr things can only be gotten from the
433          * file (ie., mu view), not from the database (mu find).  */
434         if (!(opts & MU_MSG_OPTION_HEADERS_ONLY))
435                 add_message_file_parts (items, msg, opts);
436 
437         return items;
438 
439 }
440 
441 Mu::Sexp
msg_to_sexp(MuMsg * msg,unsigned docid,MuMsgOptions opts)442 Mu::msg_to_sexp (MuMsg *msg, unsigned docid, MuMsgOptions opts)
443 {
444         return Sexp::make_list(msg_to_sexp_list(msg, docid, opts));
445 }
446