1 /**
2  * @file
3  * Create a GraphViz dot file from the NeoMutt objects
4  *
5  * @authors
6  * Copyright (C) 2018-2020 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page debug_graphviz GraphViz dot file
25  *
26  * Create a GraphViz dot file from the NeoMutt objects
27  */
28 
29 #include "config.h"
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <time.h>
35 #include "mutt/lib.h"
36 #include "address/lib.h"
37 #include "config/lib.h"
38 #include "email/lib.h"
39 #include "core/lib.h"
40 #include "conn/lib.h"
41 #include "lib.h"
42 #include "compmbox/lib.h"
43 #include "imap/lib.h"
44 #include "maildir/lib.h"
45 #include "mbox/lib.h"
46 #include "ncrypt/lib.h"
47 #include "nntp/lib.h"
48 #include "notmuch/lib.h"
49 #include "pop/lib.h"
50 #include "context.h"
51 #include "imap/adata.h"      // IWYU pragma: keep
52 #include "imap/mdata.h"      // IWYU pragma: keep
53 #include "imap/private.h"    // IWYU pragma: keep
54 #include "maildir/edata.h"   // IWYU pragma: keep
55 #include "maildir/mdata.h"   // IWYU pragma: keep
56 #include "maildir/private.h" // IWYU pragma: keep
57 #include "nntp/adata.h"      // IWYU pragma: keep
58 #include "nntp/mdata.h"      // IWYU pragma: keep
59 #include "notmuch/adata.h"   // IWYU pragma: keep
60 #include "notmuch/mdata.h"   // IWYU pragma: keep
61 #include "notmuch/private.h" // IWYU pragma: keep
62 #include "pop/adata.h"       // IWYU pragma: keep
63 #include "pop/private.h"     // IWYU pragma: keep
64 
65 // #define GV_HIDE_CONTEXT
66 #define GV_HIDE_CONTEXT_CONTENTS
67 // #define GV_HIDE_MBOX
68 // #define GV_HIDE_NEOMUTT
69 // #define GV_HIDE_CONFIG
70 // #define GV_HIDE_ADATA
71 // #define GV_HIDE_MDATA
72 
73 static void dot_email(FILE *fp, struct Email *e, struct ListHead *links);
74 static void dot_envelope(FILE *fp, struct Envelope *env, struct ListHead *links);
75 
get_content_type(enum ContentType type)76 const char *get_content_type(enum ContentType type)
77 {
78   switch (type)
79   {
80     case TYPE_OTHER:
81       return "TYPE_OTHER";
82     case TYPE_AUDIO:
83       return "TYPE_AUDIO";
84     case TYPE_APPLICATION:
85       return "TYPE_APPLICATION";
86     case TYPE_IMAGE:
87       return "TYPE_IMAGE";
88     case TYPE_MESSAGE:
89       return "TYPE_MESSAGE";
90     case TYPE_MODEL:
91       return "TYPE_MODEL";
92     case TYPE_MULTIPART:
93       return "TYPE_MULTIPART";
94     case TYPE_TEXT:
95       return "TYPE_TEXT";
96     case TYPE_VIDEO:
97       return "TYPE_VIDEO";
98     case TYPE_ANY:
99       return "TYPE_ANY";
100     default:
101       return "UNKNOWN";
102   }
103 }
104 
get_content_encoding(enum ContentEncoding enc)105 const char *get_content_encoding(enum ContentEncoding enc)
106 {
107   switch (enc)
108   {
109     case ENC_OTHER:
110       return "ENC_OTHER";
111     case ENC_7BIT:
112       return "ENC_7BIT";
113     case ENC_8BIT:
114       return "ENC_8BIT";
115     case ENC_QUOTED_PRINTABLE:
116       return "ENC_QUOTED_PRINTABLE";
117     case ENC_BASE64:
118       return "ENC_BASE64";
119     case ENC_BINARY:
120       return "ENC_BINARY";
121     case ENC_UUENCODED:
122       return "ENC_UUENCODED";
123     default:
124       return "UNKNOWN";
125   }
126 }
127 
get_content_disposition(enum ContentDisposition disp)128 const char *get_content_disposition(enum ContentDisposition disp)
129 {
130   switch (disp)
131   {
132     case DISP_INLINE:
133       return "DISP_INLINE";
134     case DISP_ATTACH:
135       return "DISP_ATTACH";
136     case DISP_FORM_DATA:
137       return "DISP_FORM_DATA";
138     case DISP_NONE:
139       return "DISP_NONE";
140     default:
141       return "UNKNOWN";
142   }
143 }
144 
add_flag(struct Buffer * buf,bool is_set,const char * name)145 void add_flag(struct Buffer *buf, bool is_set, const char *name)
146 {
147   if (!buf || !name)
148     return;
149 
150   if (is_set)
151   {
152     if (!mutt_buffer_is_empty(buf))
153       mutt_buffer_addch(buf, ',');
154     mutt_buffer_addstr(buf, name);
155   }
156 }
157 
dot_type_bool(FILE * fp,const char * name,bool val)158 static void dot_type_bool(FILE *fp, const char *name, bool val)
159 {
160   static const char *values[] = { "false", "true" };
161   fprintf(fp, "\t\t<tr>\n");
162   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
163   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
164   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", values[val]);
165   fprintf(fp, "\t\t</tr>\n");
166 }
167 
168 #ifndef GV_HIDE_ADATA
dot_type_char(FILE * fp,const char * name,char ch)169 static void dot_type_char(FILE *fp, const char *name, char ch)
170 {
171   fprintf(fp, "\t\t<tr>\n");
172   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
173   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
174   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%c</td>\n", ch);
175   fprintf(fp, "\t\t</tr>\n");
176 }
177 #endif
178 
dot_type_date(char * buf,size_t buflen,time_t timestamp)179 static void dot_type_date(char *buf, size_t buflen, time_t timestamp)
180 {
181   mutt_date_localtime_format(buf, buflen, "%Y-%m-%d %H:%M:%S", timestamp);
182 }
183 
dot_type_file(FILE * fp,const char * name,FILE * struct_fp)184 static void dot_type_file(FILE *fp, const char *name, FILE *struct_fp)
185 {
186   fprintf(fp, "\t\t<tr>\n");
187   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
188   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
189   if (struct_fp)
190   {
191     fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%p (%d)</td>\n",
192             (void *) struct_fp, fileno(struct_fp));
193   }
194   else
195   {
196     fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">NULL</td>\n");
197   }
198   fprintf(fp, "\t\t</tr>\n");
199 }
200 
dot_type_number(FILE * fp,const char * name,int num)201 static void dot_type_number(FILE *fp, const char *name, int num)
202 {
203   fprintf(fp, "\t\t<tr>\n");
204   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
205   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
206   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%d</td>\n", num);
207   fprintf(fp, "\t\t</tr>\n");
208 }
209 
dot_type_string_escape(char * buf,size_t buflen)210 static void dot_type_string_escape(char *buf, size_t buflen)
211 {
212   for (; buf[0]; buf++)
213   {
214     if (buf[0] == '<')
215       mutt_str_inline_replace(buf, buflen, 1, "&lt;");
216     else if (buf[0] == '>')
217       mutt_str_inline_replace(buf, buflen, 1, "&gt;");
218     else if (buf[0] == '&')
219       mutt_str_inline_replace(buf, buflen, 1, "&amp;");
220   }
221 }
222 
dot_type_string(FILE * fp,const char * name,const char * str,bool force)223 static void dot_type_string(FILE *fp, const char *name, const char *str, bool force)
224 {
225   if ((!str || (str[0] == '\0')) && !force)
226     return;
227 
228   char buf[1024] = "[NULL]";
229 
230   if (str)
231   {
232     mutt_str_copy(buf, str, sizeof(buf));
233     dot_type_string_escape(buf, sizeof(buf));
234   }
235 
236   bool quoted = ((buf[0] != '[') && (buf[0] != '*'));
237 
238   fprintf(fp, "\t\t<tr>\n");
239   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
240   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
241   if (quoted)
242     fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">\"%s\"</td>\n", buf);
243   else
244     fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", buf);
245   fprintf(fp, "\t\t</tr>\n");
246 }
247 
248 #ifndef GV_HIDE_MDATA
dot_type_umask(char * buf,size_t buflen,int umask)249 static void dot_type_umask(char *buf, size_t buflen, int umask)
250 {
251   snprintf(buf, buflen, "0%03o", umask);
252 }
253 #endif
254 
dot_ptr_name(char * buf,size_t buflen,const void * ptr)255 static void dot_ptr_name(char *buf, size_t buflen, const void *ptr)
256 {
257   snprintf(buf, buflen, "obj_%p", ptr);
258 }
259 
dot_ptr(FILE * fp,const char * name,void * ptr,const char * colour)260 static void dot_ptr(FILE *fp, const char *name, void *ptr, const char *colour)
261 {
262   fprintf(fp, "\t\t<tr>\n");
263   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
264   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
265   if (colour && ptr)
266   {
267     fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\" bgcolor=\"%s\">%p</td>\n",
268             colour, ptr);
269   }
270   else
271   {
272     fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%p</td>\n", ptr);
273   }
274   fprintf(fp, "\t\t</tr>\n");
275 }
276 
dot_add_link(struct ListHead * links,void * src,void * dst,const char * label,bool back,const char * colour)277 static void dot_add_link(struct ListHead *links, void *src, void *dst,
278                          const char *label, bool back, const char *colour)
279 {
280   if (!src || !dst)
281     return;
282   if (!colour)
283     colour = "#c0c0c0";
284 
285   char obj1[16] = { 0 };
286   char obj2[16] = { 0 };
287   char text[256] = { 0 };
288   char lstr[128] = { 0 };
289 
290   dot_ptr_name(obj1, sizeof(obj1), src);
291   dot_ptr_name(obj2, sizeof(obj2), dst);
292 
293   if (label)
294     snprintf(lstr, sizeof(lstr), "edgetooltip=\"%s\"", label);
295 
296   snprintf(text, sizeof(text), "%s -> %s [ %s %s color=\"%s\" ]", obj1, obj2,
297            back ? "dir=back" : "", lstr, colour);
298   mutt_list_insert_tail(links, mutt_str_dup(text));
299 }
300 
dot_graph_header(FILE * fp)301 static void dot_graph_header(FILE *fp)
302 {
303   fprintf(fp, "digraph neomutt\n");
304   fprintf(fp, "{\n\n");
305 
306   fprintf(fp, "\tgraph [\n");
307   fprintf(fp, "\t\trankdir=\"TB\"\n");
308   fprintf(fp, "\t\tnodesep=\"0.5\"\n");
309   fprintf(fp, "\t\tranksep=\"0.5\"\n");
310   fprintf(fp, "\t];\n");
311   fprintf(fp, "\n");
312   fprintf(fp, "\tnode [\n");
313   fprintf(fp, "\t\tshape=\"plain\"\n");
314   fprintf(fp, "\t];\n");
315   fprintf(fp, "\n");
316   fprintf(fp, "\tedge [\n");
317   fprintf(fp, "\t\tpenwidth=\"4.5\"\n");
318   fprintf(fp, "\t\tarrowsize=\"1.0\"\n");
319   fprintf(fp, "\t\tcolor=\"#c0c0c0\"\n");
320   fprintf(fp, "\t];\n");
321   fprintf(fp, "\n");
322 }
323 
dot_graph_footer(FILE * fp,struct ListHead * links)324 static void dot_graph_footer(FILE *fp, struct ListHead *links)
325 {
326   fprintf(fp, "\n");
327   struct ListNode *np = NULL;
328   STAILQ_FOREACH(np, links, entries)
329   {
330     fprintf(fp, "\t%s;\n", np->data);
331   }
332   fprintf(fp, "\n}\n");
333 }
334 
dot_object_header(FILE * fp,const void * ptr,const char * name,const char * colour)335 static void dot_object_header(FILE *fp, const void *ptr, const char *name, const char *colour)
336 {
337   char obj[16] = { 0 };
338   dot_ptr_name(obj, sizeof(obj), ptr);
339 
340   if (!colour)
341     colour = "#ffff80";
342 
343   fprintf(fp, "\t%s [\n", obj);
344   fprintf(fp, "\t\tlabel=<<table cellspacing=\"0\" border=\"1\" rows=\"*\" "
345               "color=\"#d0d0d0\">\n");
346   fprintf(fp, "\t\t<tr>\n");
347   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\" bgcolor=\"%s\" port=\"top\" colspan=\"3\"><font color=\"#000000\" point-size=\"20\"><b>%s</b></font> <font point-size=\"12\">(%p)</font></td>\n",
348           colour, name, ptr);
349   fprintf(fp, "\t\t</tr>\n");
350 }
351 
dot_object_footer(FILE * fp)352 static void dot_object_footer(FILE *fp)
353 {
354   fprintf(fp, "\t\t</table>>\n");
355   fprintf(fp, "\t];\n");
356   fprintf(fp, "\n");
357 }
358 
dot_node(FILE * fp,void * ptr,const char * name,const char * colour)359 static void dot_node(FILE *fp, void *ptr, const char *name, const char *colour)
360 {
361   char obj[16] = { 0 };
362   dot_ptr_name(obj, sizeof(obj), ptr);
363 
364   fprintf(fp, "\t%s [\n", obj);
365   fprintf(fp, "\t\tlabel=<<table cellspacing=\"0\" border=\"1\" rows=\"*\" "
366               "color=\"#d0d0d0\">\n");
367   fprintf(fp, "\t\t<tr>\n");
368   fprintf(fp, "\t\t\t<td border=\"0\" bgcolor=\"%s\" port=\"top\"><font color=\"#000000\" point-size=\"20\"><b>%s</b></font></td>\n",
369           colour, name);
370   fprintf(fp, "\t\t</tr>\n");
371   dot_object_footer(fp);
372 }
373 
dot_node_link(FILE * fp,void * ptr,const char * name,void * link,const char * colour)374 static void dot_node_link(FILE *fp, void *ptr, const char *name, void *link, const char *colour)
375 {
376   char obj[16] = { 0 };
377   dot_ptr_name(obj, sizeof(obj), ptr);
378 
379   fprintf(fp, "\t%s [\n", obj);
380   fprintf(fp, "\t\tlabel=<<table cellspacing=\"0\" border=\"1\" rows=\"*\" "
381               "color=\"#d0d0d0\">\n");
382   fprintf(fp, "\t\t<tr>\n");
383   fprintf(fp, "\t\t\t<td border=\"0\" bgcolor=\"%s\" port=\"top\"><font color=\"#000000\" point-size=\"20\"><b>%s</b></font></td>\n",
384           colour, name);
385   fprintf(fp, "\t\t</tr>\n");
386 
387   fprintf(fp, "\t\t<tr>\n");
388   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\" bgcolor=\"%s\">%p</td>\n", colour, link);
389   fprintf(fp, "\t\t</tr>\n");
390 
391   dot_object_footer(fp);
392 }
393 
dot_path_fs(char * buf,size_t buflen,const char * path)394 static void dot_path_fs(char *buf, size_t buflen, const char *path)
395 {
396   if (!path)
397   {
398     buf[0] = '\0';
399     return;
400   }
401 
402   const char *slash = strrchr(path, '/');
403   if (slash)
404     slash++;
405   else
406     slash = path;
407 
408   mutt_str_copy(buf, slash, buflen);
409 }
410 
dot_path_imap(char * buf,size_t buflen,const char * path)411 static void dot_path_imap(char *buf, size_t buflen, const char *path)
412 {
413   char tmp[1024] = { 0 };
414   mutt_str_copy(tmp, path, sizeof(tmp));
415 
416   struct Url *u = url_parse(tmp);
417 
418   if (u->path && (u->path[0] != '\0'))
419     mutt_str_copy(buf, u->path, buflen);
420   else
421     snprintf(buf, buflen, "%s:%s", u->host, u->user);
422 
423   url_free(&u);
424 }
425 
426 #ifndef GV_HIDE_CONFIG
dot_config(FILE * fp,const char * name,int type,struct ConfigSubset * sub,struct ListHead * links)427 static void dot_config(FILE *fp, const char *name, int type,
428                        struct ConfigSubset *sub, struct ListHead *links)
429 {
430   if (!sub)
431     return;
432 
433   struct Buffer value = mutt_buffer_make(256);
434   dot_object_header(fp, (void *) name, "Config", "#ffff80");
435   dot_type_string(fp, "scope", sub->name, true);
436 
437   if (sub->name)
438   {
439     char scope[256];
440     snprintf(scope, sizeof(scope), "%s:", sub->name);
441 
442     struct HashElem **list = get_elem_list(sub->cs);
443     for (size_t i = 0; list[i]; i++)
444     {
445       struct HashElem *item = list[i];
446       if ((item->type & type) == 0)
447         continue;
448 
449       const char *iname = item->key.strkey;
450       size_t slen = strlen(scope);
451       if (mutt_str_startswith(iname, scope) != 0)
452       {
453         if (strchr(iname + slen, ':'))
454           continue;
455         if ((DTYPE(item->type) == DT_STRING) && (item->type & DT_SENSITIVE))
456         {
457           dot_type_string(fp, iname + slen, "***", true);
458         }
459         else
460         {
461           mutt_buffer_reset(&value);
462           cs_subset_he_string_get(sub, item, &value);
463           dot_type_string(fp, iname + slen, value.data, true);
464         }
465       }
466     }
467     FREE(&list);
468   }
469   else
470   {
471     struct HashElem **list = get_elem_list(sub->cs);
472     int i = 0;
473     for (; list[i]; i++)
474       ; // do nothing
475 
476     dot_type_number(fp, "count", i);
477     FREE(&list);
478   }
479 
480   dot_object_footer(fp);
481   mutt_buffer_dealloc(&value);
482 }
483 #endif
484 
dot_comp(FILE * fp,struct CompressInfo * ci,struct ListHead * links)485 static void dot_comp(FILE *fp, struct CompressInfo *ci, struct ListHead *links)
486 {
487   dot_object_header(fp, ci, "CompressInfo", "#c0c060");
488   dot_type_string(fp, "append", ci->cmd_append, true);
489   dot_type_string(fp, "close", ci->cmd_close, true);
490   dot_type_string(fp, "open", ci->cmd_open, true);
491   dot_object_footer(fp);
492 }
493 
dot_mailbox_type(FILE * fp,const char * name,enum MailboxType type)494 static void dot_mailbox_type(FILE *fp, const char *name, enum MailboxType type)
495 {
496   const char *typestr = NULL;
497 
498   switch (type)
499   {
500     case MUTT_MBOX:
501       typestr = "MBOX";
502       break;
503     case MUTT_MMDF:
504       typestr = "MMDF";
505       break;
506     case MUTT_MH:
507       typestr = "MH";
508       break;
509     case MUTT_MAILDIR:
510       typestr = "MAILDIR";
511       break;
512     case MUTT_NNTP:
513       typestr = "NNTP";
514       break;
515     case MUTT_IMAP:
516       typestr = "IMAP";
517       break;
518     case MUTT_NOTMUCH:
519       typestr = "NOTMUCH";
520       break;
521     case MUTT_POP:
522       typestr = "POP";
523       break;
524     case MUTT_COMPRESSED:
525       typestr = "COMPRESSED";
526       break;
527     default:
528       typestr = "UNKNOWN";
529   }
530 
531   fprintf(fp, "\t\t<tr>\n");
532   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", name);
533   fprintf(fp, "\t\t\t<td border=\"0\">=</td>\n");
534   fprintf(fp, "\t\t\t<td border=\"0\" align=\"left\">%s</td>\n", typestr);
535   fprintf(fp, "\t\t</tr>\n");
536 }
537 
538 #ifndef GV_HIDE_MDATA
dot_mailbox_imap(FILE * fp,struct ImapMboxData * mdata,struct ListHead * links)539 static void dot_mailbox_imap(FILE *fp, struct ImapMboxData *mdata, struct ListHead *links)
540 {
541   dot_object_header(fp, mdata, "ImapMboxData", "#60c060");
542   dot_type_string(fp, "name", mdata->name, true);
543   dot_type_string(fp, "munge_name", mdata->munge_name, true);
544   dot_type_string(fp, "real_name", mdata->real_name, true);
545   dot_object_footer(fp);
546 }
547 
dot_mailbox_maildir(FILE * fp,struct MaildirMboxData * mdata,struct ListHead * links)548 static void dot_mailbox_maildir(FILE *fp, struct MaildirMboxData *mdata, struct ListHead *links)
549 {
550   char buf[64] = { 0 };
551 
552   dot_object_header(fp, mdata, "MaildirMboxData", "#60c060");
553 
554   dot_type_date(buf, sizeof(buf), mdata->mtime_cur.tv_sec);
555   dot_type_string(fp, "mtime_cur", buf, true);
556 
557   dot_type_umask(buf, sizeof(buf), mdata->mh_umask);
558   dot_type_string(fp, "mh_umask", buf, true);
559   dot_object_footer(fp);
560 }
561 
dot_mailbox_mbox(FILE * fp,struct MboxAccountData * mdata,struct ListHead * links)562 static void dot_mailbox_mbox(FILE *fp, struct MboxAccountData *mdata, struct ListHead *links)
563 {
564   char buf[64] = { 0 };
565 
566   dot_object_header(fp, mdata, "MboxAccountData", "#60c060");
567   dot_ptr(fp, "fp", mdata->fp, NULL);
568 
569   dot_type_date(buf, sizeof(buf), mdata->atime.tv_sec);
570   dot_type_string(fp, "atime", buf, true);
571 
572   dot_object_footer(fp);
573 }
574 
dot_mailbox_nntp(FILE * fp,struct NntpMboxData * mdata,struct ListHead * links)575 static void dot_mailbox_nntp(FILE *fp, struct NntpMboxData *mdata, struct ListHead *links)
576 {
577   dot_object_header(fp, mdata, "NntpMboxData", "#60c060");
578   dot_type_string(fp, "group", mdata->group, true);
579   dot_type_string(fp, "desc", mdata->desc, true);
580 
581   dot_type_number(fp, "first_message", mdata->first_message);
582   dot_type_number(fp, "last_message", mdata->last_message);
583   dot_type_number(fp, "last_loaded", mdata->last_loaded);
584   dot_type_number(fp, "last_cached", mdata->last_cached);
585   dot_type_number(fp, "unread", mdata->unread);
586 
587   dot_type_bool(fp, "subscribed", mdata->subscribed);
588   dot_type_bool(fp, "has_new_mail", mdata->has_new_mail);
589   dot_type_bool(fp, "allowed", mdata->allowed);
590   dot_type_bool(fp, "deleted", mdata->deleted);
591 
592   dot_object_footer(fp);
593 }
594 
dot_mailbox_notmuch(FILE * fp,struct NmMboxData * mdata,struct ListHead * links)595 static void dot_mailbox_notmuch(FILE *fp, struct NmMboxData *mdata, struct ListHead *links)
596 {
597   dot_object_header(fp, mdata, "NmMboxData", "#60c060");
598   dot_type_number(fp, "db_limit", mdata->db_limit);
599   dot_object_footer(fp);
600 }
601 
dot_mailbox_pop(FILE * fp,struct PopAccountData * adata,struct ListHead * links)602 static void dot_mailbox_pop(FILE *fp, struct PopAccountData *adata, struct ListHead *links)
603 {
604   dot_object_header(fp, adata, "PopAccountData", "#60c060");
605   dot_ptr(fp, "conn", adata->conn, "#ff8080");
606   dot_object_footer(fp);
607 }
608 #endif
609 
dot_mailbox(FILE * fp,struct Mailbox * m,struct ListHead * links)610 static void dot_mailbox(FILE *fp, struct Mailbox *m, struct ListHead *links)
611 {
612   char buf[64] = { 0 };
613 
614   dot_object_header(fp, m, "Mailbox", "#80ff80");
615   dot_mailbox_type(fp, "type", m->type);
616   dot_type_string(fp, "name", m->name, false);
617 
618   if ((m->type == MUTT_IMAP) || (m->type == MUTT_POP))
619   {
620     dot_path_imap(buf, sizeof(buf), mutt_buffer_string(&m->pathbuf));
621     dot_type_string(fp, "pathbuf", buf, true);
622     dot_path_imap(buf, sizeof(buf), m->realpath);
623     dot_type_string(fp, "realpath", buf, true);
624   }
625   else
626   {
627     dot_path_fs(buf, sizeof(buf), mutt_buffer_string(&m->pathbuf));
628     dot_type_string(fp, "pathbuf", buf, true);
629     dot_path_fs(buf, sizeof(buf), m->realpath);
630     dot_type_string(fp, "realpath", buf, true);
631   }
632 
633 #ifdef GV_HIDE_MDATA
634   dot_ptr(fp, "mdata", m->mdata, NULL);
635 #endif
636   dot_ptr(fp, "account", m->account, "#80ffff");
637   dot_type_number(fp, "opened", m->opened);
638 
639   dot_type_number(fp, "msg_count", m->msg_count);
640   // dot_type_number(fp, "msg_unread", m->msg_unread);
641   // dot_type_number(fp, "msg_flagged", m->msg_flagged);
642   // dot_type_number(fp, "msg_new", m->msg_new);
643   // dot_type_number(fp, "msg_deleted", m->msg_deleted);
644   // dot_type_number(fp, "msg_tagged", m->msg_tagged);
645 
646   dot_ptr(fp, "emails", m->emails, NULL);
647   dot_type_number(fp, "email_max", m->email_max);
648   dot_ptr(fp, "v2r", m->v2r, NULL);
649   dot_type_number(fp, "vcount", m->vcount);
650 
651   dot_object_footer(fp);
652 
653   // dot_add_link(links, m, m->mdata, false, NULL);
654 
655 #ifndef GV_HIDE_MDATA
656   if (m->mdata)
657   {
658     if (m->type == MUTT_MAILDIR)
659       dot_mailbox_maildir(fp, m->mdata, links);
660     else if (m->type == MUTT_IMAP)
661       dot_mailbox_imap(fp, m->mdata, links);
662     else if (m->type == MUTT_POP)
663       dot_mailbox_pop(fp, m->mdata, links);
664     else if (m->type == MUTT_MBOX)
665       dot_mailbox_mbox(fp, m->mdata, links);
666     else if (m->type == MUTT_NNTP)
667       dot_mailbox_nntp(fp, m->mdata, links);
668     else if (m->type == MUTT_NOTMUCH)
669       dot_mailbox_notmuch(fp, m->mdata, links);
670 
671     dot_add_link(links, m, m->mdata, "Mailbox->mdata", false, NULL);
672   }
673 #endif
674 
675   if (m->compress_info)
676   {
677     dot_comp(fp, m->compress_info, links);
678     dot_add_link(links, m, m->compress_info, "Mailbox->compress_info", false, NULL);
679   }
680 
681 #ifndef GV_HIDE_CONFIG
682   if (m->name)
683   {
684     dot_config(fp, m->name, DT_INHERIT_MBOX, m->sub, links);
685     dot_add_link(links, m, m->name, "Mailbox Config", false, NULL);
686   }
687 #endif
688 }
689 
dot_mailbox_node(FILE * fp,struct MailboxNode * mn,struct ListHead * links)690 static void dot_mailbox_node(FILE *fp, struct MailboxNode *mn, struct ListHead *links)
691 {
692   dot_node(fp, mn, "MN", "#80ff80");
693 
694   dot_mailbox(fp, mn->mailbox, links);
695 
696   dot_add_link(links, mn, mn->mailbox, "MailboxNode->mailbox", false, NULL);
697 
698   struct Buffer buf;
699   mutt_buffer_init(&buf);
700 
701   char name[256] = { 0 };
702   mutt_buffer_addstr(&buf, "{ rank=same ");
703 
704   dot_ptr_name(name, sizeof(name), mn);
705   mutt_buffer_add_printf(&buf, "%s ", name);
706 
707   dot_ptr_name(name, sizeof(name), mn->mailbox);
708   mutt_buffer_add_printf(&buf, "%s ", name);
709 
710 #ifndef GV_HIDE_MDATA
711   if (mn->mailbox->mdata)
712   {
713     dot_ptr_name(name, sizeof(name), mn->mailbox->mdata);
714     mutt_buffer_add_printf(&buf, "%s ", name);
715   }
716 #endif
717 
718 #ifndef GV_HIDE_CONFIG
719   if (mn->mailbox->name)
720   {
721     dot_ptr_name(name, sizeof(name), mn->mailbox->name);
722     mutt_buffer_add_printf(&buf, "%s ", name);
723   }
724 #endif
725 
726   mutt_buffer_addstr(&buf, "}");
727 
728   mutt_list_insert_tail(links, mutt_str_dup(buf.data));
729   mutt_buffer_dealloc(&buf);
730 }
731 
dot_mailbox_list(FILE * fp,struct MailboxList * ml,struct ListHead * links,bool abbr)732 static void dot_mailbox_list(FILE *fp, struct MailboxList *ml, struct ListHead *links, bool abbr)
733 {
734   struct MailboxNode *prev = NULL;
735   struct MailboxNode *np = NULL;
736   STAILQ_FOREACH(np, ml, entries)
737   {
738     if (abbr)
739       dot_node_link(fp, np, "MN", np->mailbox, "#80ff80");
740     else
741       dot_mailbox_node(fp, np, links);
742     if (prev)
743       dot_add_link(links, prev, np, "MailboxNode->next", false, NULL);
744     prev = np;
745   }
746 }
747 
748 #ifndef GV_HIDE_ADATA
dot_connection(FILE * fp,struct Connection * c,struct ListHead * links)749 static void dot_connection(FILE *fp, struct Connection *c, struct ListHead *links)
750 {
751   dot_object_header(fp, c, "Connection", "#ff8080");
752   // dot_ptr(fp, "sockdata", c->sockdata, "#60c0c0");
753   dot_type_number(fp, "fd", c->fd);
754   dot_object_footer(fp);
755 
756   dot_object_header(fp, c->inbuf, "ConnAccount", "#ff8080");
757   dot_type_string(fp, "user", c->account.user, true);
758   dot_type_string(fp, "host", c->account.host, true);
759   dot_type_number(fp, "port", c->account.port);
760   dot_object_footer(fp);
761 
762   dot_add_link(links, c, c->inbuf, "Connection.ConnAccount", false, NULL);
763 }
764 
dot_account_imap(FILE * fp,struct ImapAccountData * adata,struct ListHead * links)765 static void dot_account_imap(FILE *fp, struct ImapAccountData *adata, struct ListHead *links)
766 {
767   dot_object_header(fp, adata, "ImapAccountData", "#60c0c0");
768   // dot_type_string(fp, "mbox_name", adata->mbox_name, true);
769   // dot_type_string(fp, "login", adata->conn->account.login, true);
770   dot_type_string(fp, "user", adata->conn->account.user, true);
771   dot_type_string(fp, "pass", adata->conn->account.pass[0] ? "***" : "", true);
772   dot_type_number(fp, "port", adata->conn->account.port);
773   // dot_ptr(fp, "conn", adata->conn, "#ff8080");
774   dot_type_bool(fp, "unicode", adata->unicode);
775   dot_type_bool(fp, "qresync", adata->qresync);
776   dot_type_char(fp, "seqid", adata->seqid);
777   dot_ptr(fp, "mailbox", adata->mailbox, "#80ff80");
778   dot_object_footer(fp);
779 
780   if (adata->conn)
781   {
782     dot_connection(fp, adata->conn, links);
783     dot_add_link(links, adata, adata->conn, "ImapAccountData->conn", false, NULL);
784   }
785 }
786 
dot_account_mbox(FILE * fp,struct MboxAccountData * adata,struct ListHead * links)787 static void dot_account_mbox(FILE *fp, struct MboxAccountData *adata, struct ListHead *links)
788 {
789   char buf[64] = { 0 };
790 
791   dot_object_header(fp, adata, "MboxAccountData", "#60c0c0");
792   dot_ptr(fp, "fp", adata->fp, NULL);
793 
794   dot_type_date(buf, sizeof(buf), adata->atime.tv_sec);
795   dot_type_string(fp, "atime", buf, true);
796   dot_type_bool(fp, "locked", adata->locked);
797   dot_type_bool(fp, "append", adata->append);
798 
799   dot_object_footer(fp);
800 }
801 
dot_account_nntp(FILE * fp,struct NntpAccountData * adata,struct ListHead * links)802 static void dot_account_nntp(FILE *fp, struct NntpAccountData *adata, struct ListHead *links)
803 {
804   dot_object_header(fp, adata, "NntpAccountData", "#60c0c0");
805   dot_type_number(fp, "groups_num", adata->groups_num);
806 
807   dot_type_bool(fp, "hasCAPABILITIES", adata->hasCAPABILITIES);
808   dot_type_bool(fp, "hasSTARTTLS", adata->hasSTARTTLS);
809   dot_type_bool(fp, "hasDATE", adata->hasDATE);
810   dot_type_bool(fp, "hasLIST_NEWSGROUPS", adata->hasLIST_NEWSGROUPS);
811   dot_type_bool(fp, "hasXGTITLE", adata->hasXGTITLE);
812   dot_type_bool(fp, "hasLISTGROUP", adata->hasLISTGROUP);
813   dot_type_bool(fp, "hasLISTGROUPrange", adata->hasLISTGROUPrange);
814   dot_type_bool(fp, "hasOVER", adata->hasOVER);
815   dot_type_bool(fp, "hasXOVER", adata->hasXOVER);
816   dot_type_bool(fp, "cacheable", adata->cacheable);
817   dot_type_bool(fp, "newsrc_modified", adata->newsrc_modified);
818 
819   dot_type_string(fp, "authenticators", adata->authenticators, true);
820   dot_type_string(fp, "overview_fmt", adata->overview_fmt, true);
821   dot_type_string(fp, "newsrc_file", adata->newsrc_file, true);
822   dot_type_file(fp, "newsrc_fp", adata->fp_newsrc);
823 
824   dot_type_number(fp, "groups_num", adata->groups_num);
825   dot_type_number(fp, "groups_max", adata->groups_max);
826 
827   char buf[128];
828   dot_type_date(buf, sizeof(buf), adata->mtime);
829   dot_type_string(fp, "mtime", buf, true);
830   dot_type_date(buf, sizeof(buf), adata->newgroups_time);
831   dot_type_string(fp, "newgroups_time", buf, true);
832   dot_type_date(buf, sizeof(buf), adata->check_time);
833   dot_type_string(fp, "check_time", buf, true);
834 
835   dot_object_footer(fp);
836 
837   if (adata->conn)
838   {
839     dot_connection(fp, adata->conn, links);
840     dot_add_link(links, adata, adata->conn, "NntpAccountData->conn", false, NULL);
841   }
842 }
843 
dot_account_notmuch(FILE * fp,struct NmAccountData * adata,struct ListHead * links)844 static void dot_account_notmuch(FILE *fp, struct NmAccountData *adata, struct ListHead *links)
845 {
846   dot_object_header(fp, adata, "NmAccountData", "#60c0c0");
847   dot_ptr(fp, "db", adata->db, NULL);
848   dot_object_footer(fp);
849 }
850 
dot_account_pop(FILE * fp,struct PopAccountData * adata,struct ListHead * links)851 static void dot_account_pop(FILE *fp, struct PopAccountData *adata, struct ListHead *links)
852 {
853   char buf[64] = { 0 };
854 
855   dot_object_header(fp, adata, "PopAccountData", "#60c0c0");
856 
857   dot_type_date(buf, sizeof(buf), adata->check_time);
858   dot_type_string(fp, "check_time", buf, true);
859 
860   dot_type_string(fp, "login", adata->conn->account.login, true);
861   dot_type_string(fp, "user", adata->conn->account.user, true);
862   dot_type_string(fp, "pass", adata->conn->account.pass[0] ? "***" : "", true);
863   dot_type_number(fp, "port", adata->conn->account.port);
864   // dot_ptr(fp, "conn", adata->conn, "#ff8080");
865   dot_object_footer(fp);
866 
867   if (adata->conn)
868   {
869     dot_connection(fp, adata->conn, links);
870     dot_add_link(links, adata, adata->conn, "PopAccountData->conn", false, NULL);
871   }
872 }
873 #endif
874 
dot_account(FILE * fp,struct Account * a,struct ListHead * links)875 static void dot_account(FILE *fp, struct Account *a, struct ListHead *links)
876 {
877   dot_object_header(fp, a, "Account", "#80ffff");
878   dot_mailbox_type(fp, "type", a->type);
879   dot_type_string(fp, "name", a->name, true);
880   // dot_ptr(fp, "adata", a->adata, "#60c0c0");
881   dot_object_footer(fp);
882 
883 #ifndef GV_HIDE_ADATA
884   if (a->adata)
885   {
886     if (a->type == MUTT_IMAP)
887       dot_account_imap(fp, a->adata, links);
888     else if (a->type == MUTT_POP)
889       dot_account_pop(fp, a->adata, links);
890     else if (a->type == MUTT_MBOX)
891       dot_account_mbox(fp, a->adata, links);
892     else if (a->type == MUTT_NNTP)
893       dot_account_nntp(fp, a->adata, links);
894     else if (a->type == MUTT_NOTMUCH)
895       dot_account_notmuch(fp, a->adata, links);
896 
897     dot_add_link(links, a, a->adata, "Account->adata", false, NULL);
898   }
899 #endif
900 
901 #ifndef GV_HIDE_CONFIG
902   if (a->name)
903   {
904     dot_config(fp, a->name, DT_INHERIT_ACC, a->sub, links);
905     dot_add_link(links, a, a->name, "Config", false, NULL);
906 
907     char name[256] = { 0 };
908     struct Buffer buf;
909     mutt_buffer_init(&buf);
910 
911     mutt_buffer_addstr(&buf, "{ rank=same ");
912 
913     dot_ptr_name(name, sizeof(name), a);
914     mutt_buffer_add_printf(&buf, "%s ", name);
915 
916     dot_ptr_name(name, sizeof(name), a->name);
917     mutt_buffer_add_printf(&buf, "%s ", name);
918 
919     mutt_buffer_addstr(&buf, "}");
920     mutt_list_insert_tail(links, mutt_str_dup(buf.data));
921     mutt_buffer_dealloc(&buf);
922   }
923 #endif
924 
925   struct MailboxNode *first = STAILQ_FIRST(&a->mailboxes);
926   dot_add_link(links, a, first, "Account->mailboxes", false, NULL);
927   dot_mailbox_list(fp, &a->mailboxes, links, false);
928 }
929 
dot_account_list(FILE * fp,struct AccountList * al,struct ListHead * links)930 static void dot_account_list(FILE *fp, struct AccountList *al, struct ListHead *links)
931 {
932   struct Account *prev = NULL;
933   struct Account *np = NULL;
934   TAILQ_FOREACH(np, al, entries)
935   {
936 #ifdef GV_HIDE_MBOX
937     if (np->type == MUTT_MBOX)
938       continue;
939 #endif
940     dot_account(fp, np, links);
941     if (prev)
942       dot_add_link(links, prev, np, "Account->next", false, NULL);
943 
944     prev = np;
945   }
946 }
947 
948 #ifndef GV_HIDE_CONTEXT
dot_context(FILE * fp,struct Context * ctx,struct ListHead * links)949 static void dot_context(FILE *fp, struct Context *ctx, struct ListHead *links)
950 {
951   dot_object_header(fp, ctx, "Context", "#ff80ff");
952   dot_ptr(fp, "mailbox", ctx->mailbox, "#80ff80");
953 #ifdef GV_HIDE_CONTEXT_CONTENTS
954   dot_type_number(fp, "vsize", ctx->vsize);
955   dot_type_string(fp, "pattern", ctx->pattern, true);
956   dot_type_bool(fp, "collapsed", ctx->collapsed);
957 #endif
958   dot_object_footer(fp);
959 }
960 #endif
961 
dump_graphviz(const char * title,struct Context * ctx)962 void dump_graphviz(const char *title, struct Context *ctx)
963 {
964   char name[256] = { 0 };
965   struct ListHead links = STAILQ_HEAD_INITIALIZER(links);
966 
967   time_t now = time(NULL);
968   if (title)
969   {
970     char date[128];
971     mutt_date_localtime_format(date, sizeof(date), "%R", now);
972     snprintf(name, sizeof(name), "%s-%s.gv", date, title);
973   }
974   else
975   {
976     mutt_date_localtime_format(name, sizeof(name), "%R.gv", now);
977   }
978 
979   umask(022);
980   FILE *fp = fopen(name, "w");
981   if (!fp)
982     return;
983 
984   dot_graph_header(fp);
985 
986 #ifndef GV_HIDE_NEOMUTT
987   dot_node(fp, NeoMutt, "NeoMutt", "#ffa500");
988   dot_add_link(&links, NeoMutt, TAILQ_FIRST(&NeoMutt->accounts),
989                "NeoMutt->accounts", false, NULL);
990 #ifndef GV_HIDE_CONFIG
991   dot_config(fp, (const char *) NeoMutt->sub, 0, NeoMutt->sub, &links);
992   dot_add_link(&links, NeoMutt, NeoMutt->sub, "NeoMutt Config", false, NULL);
993   struct Buffer buf = mutt_buffer_make(256);
994   char obj1[16] = { 0 };
995   char obj2[16] = { 0 };
996   dot_ptr_name(obj1, sizeof(obj1), NeoMutt);
997   dot_ptr_name(obj2, sizeof(obj2), NeoMutt->sub);
998   mutt_buffer_printf(&buf, "{ rank=same %s %s }", obj1, obj2);
999   mutt_list_insert_tail(&links, mutt_str_dup(mutt_buffer_string(&buf)));
1000   mutt_buffer_dealloc(&buf);
1001 #endif
1002 #endif
1003 
1004   dot_account_list(fp, &NeoMutt->accounts, &links);
1005 
1006 #ifndef GV_HIDE_CONTEXT
1007   if (ctx)
1008     dot_context(fp, ctx, &links);
1009 
1010 #ifndef GV_HIDE_NEOMUTT
1011   /* Globals */
1012   fprintf(fp, "\t{ rank=same ");
1013   if (ctx)
1014   {
1015     dot_ptr_name(name, sizeof(name), ctx);
1016     fprintf(fp, "%s ", name);
1017   }
1018   dot_ptr_name(name, sizeof(name), NeoMutt);
1019   fprintf(fp, "%s ", name);
1020   fprintf(fp, "}\n");
1021 #endif
1022 #endif
1023 
1024   fprintf(fp, "\t{ rank=same ");
1025   struct Account *np = NULL;
1026   TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
1027   {
1028 #ifdef GV_HIDE_MBOX
1029     if (np->type == MUTT_MBOX)
1030       continue;
1031 #endif
1032     dot_ptr_name(name, sizeof(name), np);
1033     fprintf(fp, "%s ", name);
1034   }
1035   fprintf(fp, "}\n");
1036 
1037   dot_graph_footer(fp, &links);
1038   fclose(fp);
1039   mutt_list_free(&links);
1040 }
1041 
dot_parameter_list(FILE * fp,const char * name,const struct ParameterList * pl)1042 static void dot_parameter_list(FILE *fp, const char *name, const struct ParameterList *pl)
1043 {
1044   if (!pl)
1045     return;
1046   if (TAILQ_EMPTY(pl))
1047     return;
1048 
1049   dot_object_header(fp, pl, "ParameterList", "#00ff00");
1050 
1051   struct Parameter *np = NULL;
1052   TAILQ_FOREACH(np, pl, entries)
1053   {
1054     dot_type_string(fp, np->attribute, np->value, false);
1055   }
1056 
1057   dot_object_footer(fp);
1058 }
1059 
dot_content(FILE * fp,struct Content * cont,struct ListHead * links)1060 static void dot_content(FILE *fp, struct Content *cont, struct ListHead *links)
1061 {
1062   struct Buffer buf = mutt_buffer_make(256);
1063 
1064   dot_object_header(fp, cont, "Content", "#800080");
1065 
1066   dot_type_number(fp, "hibin", cont->hibin);
1067   dot_type_number(fp, "lobin", cont->lobin);
1068   dot_type_number(fp, "nulbin", cont->nulbin);
1069   dot_type_number(fp, "crlf", cont->crlf);
1070   dot_type_number(fp, "ascii", cont->ascii);
1071   dot_type_number(fp, "linemax", cont->linemax);
1072 
1073 #define ADD_BOOL(F) add_flag(&buf, cont->F, #F)
1074   ADD_BOOL(space);
1075   ADD_BOOL(binary);
1076   ADD_BOOL(from);
1077   ADD_BOOL(dot);
1078   ADD_BOOL(cr);
1079 #undef ADD_BOOL
1080 
1081   dot_object_footer(fp);
1082 
1083   mutt_buffer_dealloc(&buf);
1084 }
1085 
dot_attach_ptr(FILE * fp,struct AttachPtr * aptr,struct ListHead * links)1086 void dot_attach_ptr(FILE *fp, struct AttachPtr *aptr, struct ListHead *links)
1087 {
1088   if (!aptr)
1089     return;
1090 
1091   struct Buffer buf = mutt_buffer_make(256);
1092 
1093   dot_object_header(fp, aptr, "AttachPtr", "#ff0000");
1094 
1095   dot_type_file(fp, "fp", aptr->fp);
1096 
1097   dot_type_string(fp, "parent_type", get_content_type(aptr->parent_type), false);
1098 
1099   dot_type_number(fp, "level", aptr->level);
1100   dot_type_number(fp, "num", aptr->num);
1101 
1102   dot_type_bool(fp, "unowned", aptr->unowned);
1103   dot_type_bool(fp, "decrypted", aptr->decrypted);
1104 
1105   dot_object_footer(fp);
1106 
1107   dot_add_link(links, aptr->body, aptr, "AttachPtr->body", true, NULL);
1108 
1109   mutt_buffer_dealloc(&buf);
1110 }
1111 
dot_body(FILE * fp,struct Body * b,struct ListHead * links,bool link_next)1112 static void dot_body(FILE *fp, struct Body *b, struct ListHead *links, bool link_next)
1113 {
1114   struct Buffer buf = mutt_buffer_make(256);
1115 
1116   dot_object_header(fp, b, "Body", "#2020ff");
1117 
1118   char file[256];
1119   dot_path_fs(file, sizeof(file), b->filename);
1120   dot_type_string(fp, "file", file, false);
1121 
1122   dot_type_string(fp, "charset", b->charset, false);
1123   dot_type_string(fp, "description", b->description, false);
1124   dot_type_string(fp, "d_filename", b->d_filename, false);
1125   dot_type_string(fp, "form_name", b->form_name, false);
1126   dot_type_string(fp, "language", b->language, false);
1127   dot_type_string(fp, "subtype", b->subtype, false);
1128   dot_type_string(fp, "xtype", b->xtype, false);
1129 
1130   dot_type_string(fp, "type", get_content_type(b->type), true);
1131   dot_type_string(fp, "encoding", get_content_encoding(b->encoding), true);
1132   dot_type_string(fp, "disposition", get_content_disposition(b->disposition), true);
1133 
1134   if (b->stamp != 0)
1135   {
1136     char arr[64];
1137     dot_type_date(arr, sizeof(arr), b->stamp);
1138     dot_type_string(fp, "stamp", arr, true);
1139   }
1140 
1141 #define ADD_BOOL(F) add_flag(&buf, b->F, #F)
1142   ADD_BOOL(attach_qualifies);
1143   ADD_BOOL(badsig);
1144   ADD_BOOL(collapsed);
1145   ADD_BOOL(deleted);
1146   ADD_BOOL(force_charset);
1147   ADD_BOOL(goodsig);
1148 #ifdef USE_AUTOCRYPT
1149   ADD_BOOL(is_autocrypt);
1150 #endif
1151   ADD_BOOL(noconv);
1152   ADD_BOOL(tagged);
1153   ADD_BOOL(unlink);
1154   ADD_BOOL(use_disp);
1155   ADD_BOOL(warnsig);
1156 #undef ADD_BOOL
1157   dot_type_string(fp, "bools",
1158                   mutt_buffer_is_empty(&buf) ? "[NONE]" : mutt_buffer_string(&buf), true);
1159 
1160   dot_type_number(fp, "attach_count", b->attach_count);
1161   dot_type_number(fp, "hdr_offset", b->hdr_offset);
1162   dot_type_number(fp, "length", b->length);
1163   dot_type_number(fp, "offset", b->offset);
1164 
1165   dot_ptr(fp, "aptr", b->aptr, "#3bcbc4");
1166   dot_object_footer(fp);
1167 
1168   if (!TAILQ_EMPTY(&b->parameter))
1169   {
1170     dot_parameter_list(fp, "parameter", &b->parameter);
1171     dot_add_link(links, b, &b->parameter, "Body->mime_headers", false, NULL);
1172   }
1173 
1174   if (b->mime_headers)
1175   {
1176     dot_envelope(fp, b->mime_headers, links);
1177     dot_add_link(links, b, b->mime_headers, "Body->mime_headers", false, NULL);
1178   }
1179 
1180   if (b->email)
1181   {
1182     dot_email(fp, b->email, links);
1183     dot_add_link(links, b, b->email, "Body->email", false, NULL);
1184   }
1185 
1186   if (b->parts)
1187   {
1188     if (!b->email)
1189       dot_body(fp, b->parts, links, true);
1190     dot_add_link(links, b, b->parts, "Body->parts", false, "#ff0000");
1191   }
1192 
1193   if (b->next && link_next)
1194   {
1195     char name[256] = { 0 };
1196     mutt_buffer_reset(&buf);
1197 
1198     mutt_buffer_addstr(&buf, "{ rank=same ");
1199 
1200     dot_ptr_name(name, sizeof(name), b);
1201     mutt_buffer_add_printf(&buf, "%s ", name);
1202 
1203     for (; b->next; b = b->next)
1204     {
1205       dot_body(fp, b->next, links, false);
1206       dot_add_link(links, b, b->next, "Body->next", false, "#008000");
1207 
1208       dot_ptr_name(name, sizeof(name), b->next);
1209       mutt_buffer_add_printf(&buf, "%s ", name);
1210     }
1211 
1212     mutt_buffer_addstr(&buf, "}");
1213     mutt_list_insert_tail(links, mutt_str_dup(buf.data));
1214   }
1215   else
1216   {
1217     if (b->content)
1218     {
1219       dot_content(fp, b->content, links);
1220       dot_add_link(links, b, b->content, "Body->content", false, NULL);
1221     }
1222 
1223     // if (b->aptr)
1224     // {
1225     //   dot_attach_ptr(fp, b->aptr, links);
1226     //   dot_add_link(links, b, b->aptr, "Body->aptr", false, NULL);
1227     // }
1228   }
1229 
1230   mutt_buffer_dealloc(&buf);
1231 }
1232 
dot_list_head(FILE * fp,const char * name,const struct ListHead * list)1233 static void dot_list_head(FILE *fp, const char *name, const struct ListHead *list)
1234 {
1235   if (!list || !name)
1236     return;
1237   if (STAILQ_EMPTY(list))
1238     return;
1239 
1240   struct Buffer buf = mutt_buffer_make(256);
1241 
1242   struct ListNode *np = NULL;
1243   STAILQ_FOREACH(np, list, entries)
1244   {
1245     if (!mutt_buffer_is_empty(&buf))
1246       mutt_buffer_addch(&buf, ',');
1247     mutt_buffer_addstr(&buf, np->data);
1248   }
1249 
1250   dot_type_string(fp, name, mutt_buffer_string(&buf), false);
1251 }
1252 
dot_addr_list(FILE * fp,const char * name,const struct AddressList * al,struct ListHead * links)1253 static void dot_addr_list(FILE *fp, const char *name,
1254                           const struct AddressList *al, struct ListHead *links)
1255 {
1256   if (!al)
1257     return;
1258   if (TAILQ_EMPTY(al))
1259     return;
1260 
1261   char buf[1024] = { 0 };
1262 
1263   mutt_addrlist_write(al, buf, sizeof(buf), true);
1264   dot_type_string(fp, name, buf, false);
1265 }
1266 
dot_envelope(FILE * fp,struct Envelope * env,struct ListHead * links)1267 static void dot_envelope(FILE *fp, struct Envelope *env, struct ListHead *links)
1268 {
1269   struct Buffer buf = mutt_buffer_make(256);
1270 
1271   dot_object_header(fp, env, "Envelope", "#ffff00");
1272 
1273 #define ADD_FLAG(F) add_flag(&buf, (env->changed & F), #F)
1274   ADD_FLAG(MUTT_ENV_CHANGED_IRT);
1275   ADD_FLAG(MUTT_ENV_CHANGED_REFS);
1276   ADD_FLAG(MUTT_ENV_CHANGED_XLABEL);
1277   ADD_FLAG(MUTT_ENV_CHANGED_SUBJECT);
1278 #undef ADD_BOOL
1279   dot_type_string(fp, "changed",
1280                   mutt_buffer_is_empty(&buf) ? "[NONE]" : mutt_buffer_string(&buf), true);
1281 
1282 #define ADDR_LIST(AL) dot_addr_list(fp, #AL, &env->AL, links)
1283   ADDR_LIST(return_path);
1284   ADDR_LIST(from);
1285   ADDR_LIST(to);
1286   ADDR_LIST(cc);
1287   ADDR_LIST(bcc);
1288   ADDR_LIST(sender);
1289   ADDR_LIST(reply_to);
1290   ADDR_LIST(mail_followup_to);
1291   ADDR_LIST(x_original_to);
1292 #undef ADDR_LIST
1293 
1294   dot_type_string(fp, "date", env->date, false);
1295   dot_type_string(fp, "disp_subj", env->disp_subj, false);
1296   dot_type_string(fp, "followup_to", env->followup_to, false);
1297   dot_type_string(fp, "list_post", env->list_post, false);
1298   dot_type_string(fp, "list_subscribe", env->list_subscribe, false);
1299   dot_type_string(fp, "list_unsubscribe", env->list_unsubscribe, false);
1300   dot_type_string(fp, "message_id", env->message_id, false);
1301   dot_type_string(fp, "newsgroups", env->newsgroups, false);
1302   dot_type_string(fp, "organization", env->organization, false);
1303   dot_type_string(fp, "real_subj", env->real_subj, false);
1304   dot_type_string(fp, "spam", mutt_buffer_string(&env->spam), false);
1305   dot_type_string(fp, "subject", env->subject, false);
1306   dot_type_string(fp, "supersedes", env->supersedes, false);
1307   dot_type_string(fp, "xref", env->xref, false);
1308   dot_type_string(fp, "x_comment_to", env->x_comment_to, false);
1309   dot_type_string(fp, "x_label", env->x_label, false);
1310 
1311   if (0)
1312   {
1313     dot_list_head(fp, "references", &env->references);
1314     dot_list_head(fp, "in_reply_to", &env->in_reply_to);
1315     dot_list_head(fp, "userhdrs", &env->userhdrs);
1316   }
1317 
1318 #ifdef USE_AUTOCRYPT
1319   dot_ptr(fp, "autocrypt", env->autocrypt, NULL);
1320   dot_ptr(fp, "autocrypt_gossip", env->autocrypt_gossip, NULL);
1321 #endif
1322 
1323   dot_object_footer(fp);
1324 
1325   mutt_buffer_dealloc(&buf);
1326 }
1327 
dot_email(FILE * fp,struct Email * e,struct ListHead * links)1328 static void dot_email(FILE *fp, struct Email *e, struct ListHead *links)
1329 {
1330   struct Buffer buf = mutt_buffer_make(256);
1331   char arr[256];
1332 
1333   dot_object_header(fp, e, "Email", "#ff80ff");
1334 
1335   dot_type_string(fp, "path", e->path, true);
1336 
1337 #define ADD_BOOL(F) add_flag(&buf, e->F, #F)
1338   ADD_BOOL(active);
1339   ADD_BOOL(attach_del);
1340   ADD_BOOL(attach_valid);
1341   ADD_BOOL(changed);
1342   ADD_BOOL(collapsed);
1343   ADD_BOOL(deleted);
1344   ADD_BOOL(display_subject);
1345   ADD_BOOL(expired);
1346   ADD_BOOL(flagged);
1347   ADD_BOOL(matched);
1348   ADD_BOOL(mime);
1349   ADD_BOOL(old);
1350   ADD_BOOL(purge);
1351   ADD_BOOL(quasi_deleted);
1352   ADD_BOOL(read);
1353   ADD_BOOL(recip_valid);
1354   ADD_BOOL(replied);
1355   ADD_BOOL(searched);
1356   ADD_BOOL(subject_changed);
1357   ADD_BOOL(superseded);
1358   ADD_BOOL(tagged);
1359   ADD_BOOL(threaded);
1360   ADD_BOOL(trash);
1361   ADD_BOOL(visible);
1362 #undef ADD_BOOL
1363   dot_type_string(fp, "bools",
1364                   mutt_buffer_is_empty(&buf) ? "[NONE]" : mutt_buffer_string(&buf), true);
1365 
1366   mutt_buffer_reset(&buf);
1367 #define ADD_BOOL(F) add_flag(&buf, (e->security & F), #F)
1368   ADD_BOOL(SEC_ENCRYPT);
1369   ADD_BOOL(SEC_SIGN);
1370   ADD_BOOL(SEC_GOODSIGN);
1371   ADD_BOOL(SEC_BADSIGN);
1372   ADD_BOOL(SEC_PARTSIGN);
1373   ADD_BOOL(SEC_SIGNOPAQUE);
1374   ADD_BOOL(SEC_KEYBLOCK);
1375   ADD_BOOL(SEC_INLINE);
1376   ADD_BOOL(SEC_OPPENCRYPT);
1377   ADD_BOOL(SEC_AUTOCRYPT);
1378   ADD_BOOL(SEC_AUTOCRYPT_OVERRIDE);
1379   ADD_BOOL(APPLICATION_PGP);
1380   ADD_BOOL(APPLICATION_SMIME);
1381   ADD_BOOL(PGP_TRADITIONAL_CHECKED);
1382 #undef ADD_BOOL
1383   dot_type_string(fp, "security",
1384                   mutt_buffer_is_empty(&buf) ? "[NONE]" : mutt_buffer_string(&buf), true);
1385 
1386   dot_type_number(fp, "num_hidden", e->num_hidden);
1387   dot_type_number(fp, "offset", e->offset);
1388   dot_type_number(fp, "lines", e->lines);
1389   dot_type_number(fp, "index", e->index);
1390   dot_type_number(fp, "msgno", e->msgno);
1391   dot_type_number(fp, "vnum", e->vnum);
1392   dot_type_number(fp, "score", e->score);
1393   dot_type_number(fp, "attach_total", e->attach_total);
1394 
1395   // struct MaildirEmailData *edata = maildir_edata_get(e);
1396   // if (edata)
1397   //   dot_type_string(fp, "maildir_flags", edata->maildir_flags, false);
1398 
1399   if (e->date_sent != 0)
1400   {
1401     char zone[32];
1402     dot_type_date(arr, sizeof(arr), e->date_sent);
1403     snprintf(zone, sizeof(zone), " (%c%02u%02u)", e->zoccident ? '-' : '+',
1404              e->zhours, e->zminutes);
1405     mutt_str_cat(arr, sizeof(arr), zone);
1406     dot_type_string(fp, "date_sent", arr, false);
1407   }
1408 
1409   if (e->received != 0)
1410   {
1411     dot_type_date(arr, sizeof(arr), e->received);
1412     dot_type_string(fp, "received", arr, false);
1413   }
1414 
1415   dot_object_footer(fp);
1416 
1417   if (e->body)
1418   {
1419     dot_body(fp, e->body, links, true);
1420     dot_add_link(links, e, e->body, "Email->body", false, NULL);
1421   }
1422 
1423   if (e->env)
1424   {
1425     dot_envelope(fp, e->env, links);
1426     dot_add_link(links, e, e->env, "Email->env", false, NULL);
1427 
1428     mutt_buffer_reset(&buf);
1429     mutt_buffer_addstr(&buf, "{ rank=same ");
1430 
1431     dot_ptr_name(arr, sizeof(arr), e);
1432     mutt_buffer_add_printf(&buf, "%s ", arr);
1433 
1434     dot_ptr_name(arr, sizeof(arr), e->env);
1435     mutt_buffer_add_printf(&buf, "%s ", arr);
1436 
1437     mutt_buffer_addstr(&buf, "}");
1438 
1439     mutt_list_insert_tail(links, mutt_str_dup(buf.data));
1440   }
1441 
1442   // struct TagList tags;
1443 
1444   mutt_buffer_dealloc(&buf);
1445 }
1446 
dump_graphviz_email(struct Email * e)1447 void dump_graphviz_email(struct Email *e)
1448 {
1449   char name[256] = { 0 };
1450   struct ListHead links = STAILQ_HEAD_INITIALIZER(links);
1451 
1452   time_t now = time(NULL);
1453   mutt_date_localtime_format(name, sizeof(name), "%R-email.gv", now);
1454 
1455   umask(022);
1456   FILE *fp = fopen(name, "w");
1457   if (!fp)
1458     return;
1459 
1460   dot_graph_header(fp);
1461 
1462   dot_email(fp, e, &links);
1463 
1464   dot_graph_footer(fp, &links);
1465   fclose(fp);
1466   mutt_list_free(&links);
1467 }
1468 
dot_attach_ptr2(FILE * fp,struct AttachPtr * aptr,struct ListHead * links)1469 static void dot_attach_ptr2(FILE *fp, struct AttachPtr *aptr, struct ListHead *links)
1470 {
1471   if (!aptr)
1472     return;
1473 
1474   struct Buffer buf = mutt_buffer_make(256);
1475 
1476   dot_object_header(fp, aptr, "AttachPtr", "#3bcbc4");
1477 
1478   dot_ptr(fp, "body", aptr->body, "#2020ff");
1479   dot_type_file(fp, "fp", aptr->fp);
1480 
1481   dot_type_string(fp, "parent_type", get_content_type(aptr->parent_type), false);
1482   dot_type_number(fp, "level", aptr->level);
1483   dot_type_number(fp, "num", aptr->num);
1484   dot_type_bool(fp, "unowned", aptr->unowned);
1485   dot_type_bool(fp, "decrypted", aptr->decrypted);
1486 
1487   // dot_type_string(fp, "tree", aptr->tree, false);
1488 
1489   dot_object_footer(fp);
1490 
1491   mutt_buffer_dealloc(&buf);
1492 }
1493 
dot_array_actx_idx(FILE * fp,struct AttachPtr ** idx,short idxlen,short idxmax,struct ListHead * links)1494 static void dot_array_actx_idx(FILE *fp, struct AttachPtr **idx, short idxlen,
1495                                short idxmax, struct ListHead *links)
1496 {
1497   dot_object_header(fp, idx, "AttachCtx-&gt;idx", "#9347de");
1498 
1499   dot_type_number(fp, "idxlen", idxlen);
1500   dot_type_number(fp, "idxmax", idxmax);
1501 
1502   char arr[32];
1503   for (size_t i = 0; i < idxmax; i++)
1504   {
1505     snprintf(arr, sizeof(arr), "idx[%ld]", i);
1506     dot_ptr(fp, arr, idx[i], "#3bcbc4");
1507   }
1508 
1509   dot_object_footer(fp);
1510 
1511   for (size_t i = 0; i < idxlen; i++)
1512   {
1513     dot_attach_ptr2(fp, idx[i], links);
1514     dot_add_link(links, idx, idx[i], "AttachCtx-&gt;idx", false, NULL);
1515   }
1516 }
1517 
dot_array_actx_v2r(FILE * fp,short * v2r,short vcount,struct ListHead * links)1518 static void dot_array_actx_v2r(FILE *fp, short *v2r, short vcount, struct ListHead *links)
1519 {
1520   dot_object_header(fp, v2r, "AttachCtx-&gt;v2r", "#9347de");
1521 
1522   dot_type_number(fp, "vcount", vcount);
1523 
1524   char arr[32];
1525   for (size_t i = 0; i < vcount; i++)
1526   {
1527     snprintf(arr, sizeof(arr), "v2r[%ld]", i);
1528     dot_type_number(fp, arr, v2r[i]);
1529   }
1530 
1531   dot_object_footer(fp);
1532 }
1533 
dot_array_actx_fp_idx(FILE * fp,FILE ** fp_idx,short fp_len,short fp_max,struct ListHead * links)1534 static void dot_array_actx_fp_idx(FILE *fp, FILE **fp_idx, short fp_len,
1535                                   short fp_max, struct ListHead *links)
1536 {
1537   dot_object_header(fp, fp_idx, "AttachCtx-&gt;fp_idx", "#f86e28");
1538 
1539   dot_type_number(fp, "fp_len", fp_len);
1540   dot_type_number(fp, "fp_max", fp_max);
1541 
1542   char arr[32];
1543   for (size_t i = 0; i < fp_max; i++)
1544   {
1545     snprintf(arr, sizeof(arr), "fp_idx[%ld]", i);
1546     dot_type_file(fp, arr, fp_idx[i]);
1547   }
1548 
1549   dot_object_footer(fp);
1550 }
1551 
dot_array_actx_body_idx(FILE * fp,struct Body ** body_idx,short body_len,short body_max,struct ListHead * links)1552 static void dot_array_actx_body_idx(FILE *fp, struct Body **body_idx, short body_len,
1553                                     short body_max, struct ListHead *links)
1554 {
1555   dot_object_header(fp, body_idx, "AttachCtx-&gt;body_idx", "#4ff270");
1556 
1557   dot_type_number(fp, "body_len", body_len);
1558   dot_type_number(fp, "body_max", body_max);
1559 
1560   char arr[32];
1561   for (size_t i = 0; i < body_max; i++)
1562   {
1563     snprintf(arr, sizeof(arr), "body_idx[%ld]", i);
1564     dot_ptr(fp, arr, body_idx[i], "#2020ff");
1565   }
1566 
1567   dot_object_footer(fp);
1568 }
1569 
dot_attach_ctx(FILE * fp,struct AttachCtx * actx,struct ListHead * links)1570 static void dot_attach_ctx(FILE *fp, struct AttachCtx *actx, struct ListHead *links)
1571 {
1572   struct Buffer buf = mutt_buffer_make(256);
1573   // char arr[256];
1574 
1575   dot_object_header(fp, actx, "AttachCtx", "#9347de");
1576 
1577   dot_ptr(fp, "email", actx->email, "#ff80ff");
1578   dot_type_file(fp, "fp_root", actx->fp_root);
1579 
1580   dot_object_footer(fp);
1581 
1582   if (actx->idx)
1583   {
1584     dot_array_actx_idx(fp, actx->idx, actx->idxlen, actx->idxmax, links);
1585     dot_add_link(links, actx, actx->idx, "AttachCtx-&gt;idx", false, NULL);
1586   }
1587 
1588   if (actx->v2r)
1589   {
1590     dot_array_actx_v2r(fp, actx->v2r, actx->vcount, links);
1591     dot_add_link(links, actx, actx->v2r, "AttachCtx-&gt;v2r", false, NULL);
1592   }
1593 
1594   if (actx->fp_idx)
1595   {
1596     dot_array_actx_fp_idx(fp, actx->fp_idx, actx->fp_len, actx->fp_max, links);
1597     dot_add_link(links, actx, actx->fp_idx, "AttachCtx-&gt;fp_idx", false, NULL);
1598   }
1599 
1600   if (actx->body_idx)
1601   {
1602     dot_array_actx_body_idx(fp, actx->body_idx, actx->body_len, actx->body_max, links);
1603     dot_add_link(links, actx, actx->body_idx, "AttachCtx-&gt;body_idx", false, NULL);
1604   }
1605 
1606   mutt_buffer_dealloc(&buf);
1607 }
1608 
dump_graphviz_attach_ctx(struct AttachCtx * actx)1609 void dump_graphviz_attach_ctx(struct AttachCtx *actx)
1610 {
1611   char name[256] = { 0 };
1612   struct ListHead links = STAILQ_HEAD_INITIALIZER(links);
1613 
1614   time_t now = time(NULL);
1615   mutt_date_localtime_format(name, sizeof(name), "%R-actx.gv", now);
1616 
1617   umask(022);
1618   FILE *fp = fopen(name, "w");
1619   if (!fp)
1620     return;
1621 
1622   dot_graph_header(fp);
1623 
1624   dot_attach_ctx(fp, actx, &links);
1625 
1626   dot_graph_footer(fp, &links);
1627   fclose(fp);
1628   mutt_list_free(&links);
1629 }
1630